Files
pad/internal/server
xarmian 3bd6244001 fix(server): force-download unknown and disallowed stored MIME (BUG-2413)
The attachment read path chose Content-Disposition from the stored MIME and
DEFAULTED unknown types to inline, flipping to attachment only for the
RenderForceDownload bucket. A legacy or mislabelled image/svg+xml, an
extensionless SVG stored as text/xml, or an unrecognized row was therefore
served inline from the app's own origin — active same-origin content, one click
away once 3c-ii's converged surface gives every row a Copy-link.

Fail closed. Content-Disposition now defaults to attachment; a row is served
inline only when its stored MIME is on the allowlist AND in an EXPLICIT
inline-safe set (MIMEEntry.ServeInline) — the passive raster/audio/video types
the app embeds, plus PDF and plain text. The set is a standalone allowlist, not
a function of RenderMode, so a future RenderInline entry can't silently
auto-inline an active type; a new type fails safe (downloads) until explicitly
listed. A MIME that isn't on the allowlist at all is additionally served as
application/octet-stream so its bytes are never echoed back as a type the
browser might act on. X-Content-Type-Options: nosniff was already set.

The gate is at the single choke point: GET, HEAD, share-link access, and the
?variant= path all flow through handleGetAttachment (the transform endpoint only
decodes images into a new raster thumbnail; the bundle/account exports never
serve individual bytes inline). Regression tests cover an SVG-labelled row, a
text/xml row, an unknown-MIME row (attachment + octet-stream), and the variant
path forced to attachment, plus PDF and plain text staying inline — GET and
HEAD. Mutation-verified: reverting to the old fail-open default fails exactly the
SVG/text-xml/unknown/variant tests. Reviewed to a fresh-angle CLEAN.

Claude-Session: https://claude.ai/code/session_01WFBYxdBuSZs2tjipATxAZu
2026-08-08 20:37:39 +00:00
..