FUTURE: file/image attachments storage backend (S3?) #38
Notifications
Total Time Spent: 1 hour 50 minutes
fen
1 hour 50 minutes
No due date set.
Dependencies
No dependencies set.
Reference: poslop/palette#38
Reference in New Issue
Block a user
Research/design task for file and image paste support: should attachments go to S3-compatible storage (MinIO self-hosted works well, already proven on this homelab via Outline) or filesystem-on-volume? Consider size limits, mime type handling, streaming, serving with correct content types, and integration with the paste cans concept. Deliverable: design doc comparing options with a recommendation.
DESIGN (2026-09-09, agreed direction):
STORAGE: filesystem on the data volume behind a storage interface (internal/store/blob.go), not S3/MinIO initially. Reasons: zero extra services, single binary stays single, k3s PVC already exists, and the interface keeps a MinIO backend possible later. Interface: Put(key, reader, size) / Get(key) / Delete(key) / Stat(key). Blob naming: / to dedupe and prevent traversal.
DATA MODEL: attachments table: id ( cuid), paste_id FK, filename (sanitized, 255 max), mime (sniffed server-side, stored), size bytes, sha256, created_at. Paste content stays in pastes.content; files are separate rows. A can (issue #4) bundles pastes; each paste in a can can have attachments, or the can itself holds them - decision during implementation.
LIMITS: 25 MB per file, 100 MB per paste total (both admin-configurable via #40 settings). Server-side mime sniffing (http.DetectContentType on first 512 bytes); extension never trusted.
ENDPOINTS:
UI: file drop zone + file picker on /new (drag-drop and click), thumbnail preview for images, file chips with size + remove. Paste view renders images inline (img tags, width-capped) and other files as download chips.
E2EE INTERACTION (#39): when encrypted, files are encrypted client-side with the same derived key before upload; blob keys stored per attachment; decrypt in browser on view.
MIGRATION/SCALE: none needed initially; the blob interface allows MinIO later without UI changes.
TASKS: storage interface + tests; attachment endpoints + tests; /f serving with hardening tests (traversal, mime spoofing, oversize); /new dropzone UI; paste view rendering; can integration (#4); docs/API.md section.
Design doc: #69 (docs/design/attachments-storage.md)
Design phase complete: #69 merged (docs/design/). Implementation remains open.
QA verdict — PR #129 (file attachments, fix-38-file-attachments)
Abuse matrix: PASS (all security checks)
<script>in filename/content escaped in chip render../, absolute paths,..%2fall neutralized); stored under attachment ID, never raw filenameBlocker found (1):
renderPastenever passed anAttachmentkey into the template data map;GetAttachmentForPastehad zero callers, so the paste.html attachment chip/image-preview block never rendered (confirmed live: noattachment-chipin a file paste's HTML view).Fix pushed (
0073335onfix-38-file-attachments):renderPastenow looks upGetAttachmentForPaste(row.ID)and adds it asAttachment, so the chip/image preview renders..iconbtn.dark-toggleCSS (3x duplicate blocks from stacked branches #127/#128) to a single occurrence.Verified locally: go vet + go test pass; live render shows
attachment-chip+<img src="/f/{id}/{name}">(served 200 image/png) for a png upload, and chip-with-filename/no-preview for an html upload.Status: no merge yet — back in QA label for re-check of the chip render.
QA re-run passed (iteration 1, renderPaste fix verified).
22c6624pushed), CI test+docker success, deployed to palette-dev.Issue #38 closed — iteration 1 complete. Deferred: multi-file, S3 backend, E2EE (#39).