Server side of haverstack/core#65 (deterministic mimeType: first-recorded wins, conflicts rejected) and haverstack/core#66 (dangerous-type forcing applies to the result from any source, safe-list preferred, nosniff mandatory).
Current divergences in src/routes/attachments.ts
- The stored-metadata source is not implemented at all. Spec §Attachments: with no query params, "the server queries the
_attachment@1 record: the stored mimeType becomes Content-Type, and the filename is taken from the requester's own _attachment@1 record." The handler never reads metadata records — it serves application/octet-stream and a bare Content-Disposition: attachment unless the caller passes ?contentType/?filename (attachments.ts:44-58). So of core #66's three sources, we implement two; the third (and the per-requester filename perspective the review affirmed) is missing.
- Forcing is a deny-list, not a safe-list.
BLOCKED_MIME_TYPES (attachments.ts:73-82) is exactly the informal deny-list core #66 calls incomplete. Decided direction prefers the safe-list: image/* (minus image/svg+xml), video/*, audio/*, application/pdf, text/plain, application/octet-stream; everything else → application/octet-stream.
- Forcing must run after whichever source produced the candidate type — the current code does apply
sanitizeMimeType to the param path and omits inference of blocked types, which happens to be result-shaped, but once the stored-metadata source lands the single application point should be explicit and tested per source.
Already correct, keep and pin: X-Content-Type-Options: nosniff on every attachment response; RFC 5987 filename encoding.
Work items
Refs haverstack/core#65, haverstack/core#66, haverstack/core#52, #32, #33.
Server side of haverstack/core#65 (deterministic
mimeType: first-recorded wins, conflicts rejected) and haverstack/core#66 (dangerous-type forcing applies to the result from any source, safe-list preferred, nosniff mandatory).Current divergences in
src/routes/attachments.ts_attachment@1record: the storedmimeTypebecomesContent-Type, and the filename is taken from the requester's own_attachment@1record." The handler never reads metadata records — it servesapplication/octet-streamand a bareContent-Disposition: attachmentunless the caller passes?contentType/?filename(attachments.ts:44-58). So of core #66's three sources, we implement two; the third (and the per-requester filename perspective the review affirmed) is missing.BLOCKED_MIME_TYPES(attachments.ts:73-82) is exactly the informal deny-list core #66 calls incomplete. Decided direction prefers the safe-list:image/*(minusimage/svg+xml),video/*,audio/*,application/pdf,text/plain,application/octet-stream; everything else →application/octet-stream.sanitizeMimeTypeto the param path and omits inference of blocked types, which happens to be result-shaped, but once the stored-metadata source lands the single application point should be explicit and tested per source.Already correct, keep and pin:
X-Content-Type-Options: nosniffon every attachment response; RFC 5987 filename encoding.Work items
mimeTypefor the fileId + requester's-own filename (falls back to first record's), per core #65_attachment@1create/update arrives via core validation (#65) — server tests: second upload metadata with conflicting type → 422, matching type → OK, served Content-Type stable regardless of requesterRefs haverstack/core#65, haverstack/core#66, haverstack/core#52, #32, #33.