PST-083: No server-side ceiling on a multipart upload body before it is spooled to disk #812

Closed
opened 2026-10-10 10:56:51 +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-083. 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: denial of service / limits
  • Location: src/postulo/config/settings/base.py (692-734)
  • Found by: configuration and dependencies

Who can do it, and what they need. Any signed-in account (or an administrator for the 512 MB backup upload). No other precondition.

How it works.

  1. Django's DATA_UPLOAD_MAX_MEMORY_SIZE (2.5 MB default) does not cover file parts; the only per-upload caps are upload.size > MAX_UPLOAD_BYTES comparisons made in the form after the body has been fully received.
  2. The attacker POSTs a multi-gigabyte file part to any upload form; Django streams it to FILE_UPLOAD_TEMP_DIR (/tmp on the container's writable layer) until the body ends or gunicorn's 120 s timeout kills the worker.
  3. Three concurrent uploads occupy all three workers and fill /tmp.

Impact. Temporary disk and worker exhaustion by an authenticated user. The threat model lists denial of service against a personal instance as out of scope and defers to the reverse proxy's limits, which the wiki documents; recorded so the synthesis can say the application itself has no body ceiling.

Verification (verdict: confirmed; severity after verification: Informational). Precondition: none beyond reaching the site and having a CSRF cookie (any GET). It is not limited to signed-in accounts or administrators, because the CSRF middleware parses multipart before authentication. Still a plain denial-of-service concern that the threat model assigns to the reverse proxy. Remediation, if wanted: a small middleware that refuses requests whose Content-Length exceeds the largest legitimate upload (the 512 MB backup route needs a higher ceiling for administrators), and a deployment note on setting the proxy body limit, for example nginx client_max_body_size or Caddy request_body max_size.

Suggested remediation. Add a small middleware that refuses a request whose Content-Length exceeds the largest cap the instance accepts (backup limit for staff, 20 MB otherwise) with a 413 before the body is read, and repeat the client_max_body_size advice in the compose file comments.

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-083**. 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:** denial of service / limits - **Location:** `src/postulo/config/settings/base.py` (692-734) - **Found by:** configuration and dependencies **Who can do it, and what they need.** Any signed-in account (or an administrator for the 512 MB backup upload). No other precondition. **How it works.** 1. Django's DATA_UPLOAD_MAX_MEMORY_SIZE (2.5 MB default) does not cover file parts; the only per-upload caps are `upload.size > MAX_UPLOAD_BYTES` comparisons made in the form after the body has been fully received. 2. The attacker POSTs a multi-gigabyte file part to any upload form; Django streams it to FILE_UPLOAD_TEMP_DIR (/tmp on the container's writable layer) until the body ends or gunicorn's 120 s timeout kills the worker. 3. Three concurrent uploads occupy all three workers and fill /tmp. **Impact.** Temporary disk and worker exhaustion by an authenticated user. The threat model lists denial of service against a personal instance as out of scope and defers to the reverse proxy's limits, which the wiki documents; recorded so the synthesis can say the application itself has no body ceiling. **Verification** (verdict: confirmed; severity after verification: Informational). Precondition: none beyond reaching the site and having a CSRF cookie (any GET). It is not limited to signed-in accounts or administrators, because the CSRF middleware parses multipart before authentication. Still a plain denial-of-service concern that the threat model assigns to the reverse proxy. Remediation, if wanted: a small middleware that refuses requests whose Content-Length exceeds the largest legitimate upload (the 512 MB backup route needs a higher ceiling for administrators), and a deployment note on setting the proxy body limit, for example nginx client_max_body_size or Caddy request_body max_size. **Suggested remediation.** Add a small middleware that refuses a request whose Content-Length exceeds the largest cap the instance accepts (backup limit for staff, 20 MB otherwise) with a 413 before the body is read, and repeat the client_max_body_size advice in the compose file comments.
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:14 +00:00
Author
Owner

Fixed in 445a16db9 on main.

Fixed in 445a16db9 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#812
No description provided.