diff --git a/.planning/todos/pending/2026-11-06-blob-writers-set-content-disposition.md b/.planning/todos/pending/2026-11-06-blob-writers-set-content-disposition.md new file mode 100644 index 00000000..82b9df47 --- /dev/null +++ b/.planning/todos/pending/2026-11-06-blob-writers-set-content-disposition.md @@ -0,0 +1,27 @@ +--- +created: 2026-11-06T08:17:49.419Z +title: Blob writers that bypass attach.Store set Content-Disposition for non-inline types +area: framework +severity: minor +g15office: ifdz5o0m +files: + - modules/lagoon/attach/inline.go:31 (IsInlineContentType), :38 (uploadWriterOptions, unexported) + - ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/collection_media_controller.go:258 (storeUpload) + - ../fonoteka.go/plugins/golem15/fonoteka/classes/album_files.go:42 (StorePublicFile) + - ../sm-user-plugin/controllers/api_controller.go:1559 (avatar upload) + - ../sm-feedback-plugin/classes/submission_store.go:131 (screenshot) +--- + +## Problem + +Follow-up of g15office knuu7fil (closed by summercms.go 27c98bf). `attach.Store` stores every non-inline type (anything but JPEG/PNG/GIF/WebP/AVIF) with `Content-Disposition: attachment`, and `attach.StaticHandler` / `StaticHandlerPublic` now send the stored disposition plus `nosniff`. Writers that call `bucket.NewWriter` / `WriteAll` themselves pass only `ContentType`, so their blobs carry no disposition and an HTML or SVG blob is served inline by the static handlers (or by a web server reading the bucket directly). + +Writers that bypass `attach.Store`: +- fonoteka.go `storeUpload` and `StorePublicFile`: accept arbitrary media, so this is the real gap. The disk-extension issue at the same call sites is the separate major todo `2026-10-28-fonoteka-uploads-use-disk-extension.md` (cf81chjk); do both together. +- sm-user-plugin avatar upload and sm-feedback-plugin screenshots: inputs are already limited to images, so this is defence in depth. + +## Solution + +- Framework (additive): export the writer-options helper, e.g. `attach.UploadWriterOptions(contentType) *blob.WriterOptions`, with README and docs updated in the same commit. Writers can also check `attach.IsInlineContentType` themselves, or switch to `attach.Store`. +- Each writer uses it. Shared plugins: additive change only, plugin commit and patch tag first, then pointer bumps in the apps as separate commits. A plugin using a new framework export needs the framework tag that has it, so raise the plugin's required version only if consumers are already on that version; otherwise inline the `IsInlineContentType` check. +- Regression test per writer: an upload whose sniffed type isn't an inline image is stored with `attachment` disposition.