PST-084: Proxy hand-off (X-Accel-Redirect, X-Sendfile) relies on the operator to re-add the file policy and nosniff #813
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#813
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-084. Severity: Informational.The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.
src/postulo/core/files.py(21-25,120-129)Who can do it, and what they need. No direct attacker; this widens the blast radius of any name-driven Content-Type issue (the Content-Type issue PST-042 (#771)) on deployments that set POSTULO_MEDIA_ACCEL_PREFIX or POSTULO_MEDIA_SENDFILE.
How it works.
serve_private_filereturns an empty HttpResponse carryingX-Accel-Redirect/X-Sendfile; nginx carries over Content-Type, Content-Disposition and Cache-Control but not Content-Security-Policy or X-Content-Type-Options.docs/anddocker/contain no nginx/Apache example doing so (grep foradd_header,X-Accel,internalfinds only PLAN.md prose).Impact. Only matters together with another weakness; on its own it is a hardening and documentation gap.
Verification (verdict: confirmed; severity after verification: Informational). Drop the 'widens the blast radius of the Content-Type issue PST-042 (#771)' framing: on the hand-off path no stranger-authored bytes are served inline under a name-derived type (uploads are attachments, renders are Postulo's own PDFs, captured renderings carry an explicit first-bytes-checked type, SVG stays in Django). What is true is narrower: with hand-off enabled the browser receives private files without
X-Content-Type-Options: nosniffand without thedefault-src 'none'; sandboxpolicy, and the repository gives the operator no proxy snippet showing theinternallocation withadd_headerlines (andalways, since nginx's add_header does not apply to non-2xx by default). …Suggested remediation. Ship a documented
location /protected-media/ { internal; add_header Content-Security-Policy "default-src 'none'; sandbox" always; add_header X-Content-Type-Options nosniff always; ... }snippet (and the Apache equivalent) in docs/ or the wiki's Hardening page, and restrict the hand-off to an allow-list of inert binary types so a document type is always streamed by Django.Fixed in
a28f8c9c4on main.