PST-084: Proxy hand-off (X-Accel-Redirect, X-Sendfile) relies on the operator to re-add the file policy and nosniff #813

Closed
opened 2026-10-10 10:56:52 +00:00 by tiagoagueda · 1 comment
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 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.


  • Severity: Informational
  • Category: deployment / documentation
  • Location: src/postulo/core/files.py (21-25,120-129)
  • Found by: file handling

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.

  1. With the hand-off enabled, serve_private_file returns an empty HttpResponse carrying X-Accel-Redirect/X-Sendfile; nginx carries over Content-Type, Content-Disposition and Cache-Control but not Content-Security-Policy or X-Content-Type-Options.
  2. The module's docstring tells the proxy to add them, but docs/ and docker/ contain no nginx/Apache example doing so (grep for add_header, X-Accel, internal finds only PLAN.md prose).
  3. Any inline file whose type came out as a document type (the Content-Type issue PST-042 (#771)) is then served without sandboxing.

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: nosniff and without the default-src 'none'; sandbox policy, and the repository gives the operator no proxy snippet showing the internal location with add_header lines (and always, 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.

Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 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. --- - **Severity:** Informational - **Category:** deployment / documentation - **Location:** `src/postulo/core/files.py` (21-25,120-129) - **Found by:** file handling **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.** 1. With the hand-off enabled, `serve_private_file` returns an empty HttpResponse carrying `X-Accel-Redirect`/`X-Sendfile`; nginx carries over Content-Type, Content-Disposition and Cache-Control but not Content-Security-Policy or X-Content-Type-Options. 2. The module's docstring tells the proxy to add them, but `docs/` and `docker/` contain no nginx/Apache example doing so (grep for `add_header`, `X-Accel`, `internal` finds only PLAN.md prose). 3. Any inline file whose type came out as a document type (the Content-Type issue PST-042 (#771)) is then served without sandboxing. **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: nosniff` and without the `default-src 'none'; sandbox` policy, and the repository gives the operator no proxy snippet showing the `internal` location with `add_header` lines (and `always`, 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.
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:15 +00:00
Author
Owner

Fixed in a28f8c9c4 on main.

Fixed in a28f8c9c4 on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#813
No description provided.