Threat model: says every cookie is HttpOnly, but the CSRF cookie is not #538

Closed
opened 2026-10-01 10:47:29 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading docs/THREAT-MODEL.md against the cookie settings.

Checked again on 2026-10-01 against main at 0a5cad587: still present. csrfToken() is now called at static/js/app.js:2094 and :2317 (two callers, not one as first written; both pass on the value read from the form field); the settings, the threat model and the test have not moved.

The threat model lists "cookies are Secure and HttpOnly" among the defences against a malicious page the person is on. Only the session cookie is HttpOnly. CSRF_COOKIE_HTTPONLY is never set, so the csrftoken cookie keeps Django's default and is readable by script. The practical exposure is nil, because the same token sits in every form's hidden csrfmiddlewaretoken field anyway, but the document states a property the instance does not have, and nothing in the application needs the cookie to be readable.

  • Where: docs/THREAT-MODEL.md:16 (the row A malicious page the person is on); config/settings/base.py:692-694, which set SESSION_COOKIE_HTTPONLY = True and the two SameSite values only; static/js/app.js:44-47 (csrfToken() reads the form field, never document.cookie) and static/js/app.js:2094 and :2317, its two callers. The allauth trust cookie and Django's message cookie follow SESSION_COOKIE_HTTPONLY, so they are covered.
  • What happens: under the production settings the response that sets csrftoken carries Secure and SameSite=Lax but not HttpOnly, contrary to the threat model. Nothing in src/postulo/static outside the vendored files, nor either browser extension, reads the cookie.
  • Why it matters: the threat model is the document operators and reviewers are told to trust; a line in it that is not true makes the rest harder to believe. Setting the flag costs nothing here, which is also why it is not a vulnerability: Django's own documentation notes that it adds no real protection when the token is in the page.
  • Fix: either set CSRF_COOKIE_HTTPONLY = True in config/settings/base.py beside SESSION_COOKIE_HTTPONLY (safe, since app.js takes the token from the form), or reword the threat model to "the session cookie is HttpOnly; every cookie is Secure". The first keeps the sentence true as written.

Done when

  • tests/security/test_requests.py asserts CSRF_COOKIE_HTTPONLY alongside the existing SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE check at line 189 (fails today), or the threat model no longer claims it.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading `docs/THREAT-MODEL.md` against the cookie settings. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. `csrfToken()` is now called at `static/js/app.js:2094` and `:2317` (two callers, not one as first written; both pass on the value read from the form field); the settings, the threat model and the test have not moved. The threat model lists "cookies are `Secure` and `HttpOnly`" among the defences against a malicious page the person is on. Only the session cookie is `HttpOnly`. `CSRF_COOKIE_HTTPONLY` is never set, so the `csrftoken` cookie keeps Django's default and is readable by script. The practical exposure is nil, because the same token sits in every form's hidden `csrfmiddlewaretoken` field anyway, but the document states a property the instance does not have, and nothing in the application needs the cookie to be readable. - **Where:** `docs/THREAT-MODEL.md:16` (the row *A malicious page the person is on*); `config/settings/base.py:692-694`, which set `SESSION_COOKIE_HTTPONLY = True` and the two `SameSite` values only; `static/js/app.js:44-47` (`csrfToken()` reads the form field, never `document.cookie`) and `static/js/app.js:2094` and `:2317`, its two callers. The allauth trust cookie and Django's message cookie follow `SESSION_COOKIE_HTTPONLY`, so they are covered. - **What happens:** under the production settings the response that sets `csrftoken` carries `Secure` and `SameSite=Lax` but not `HttpOnly`, contrary to the threat model. Nothing in `src/postulo/static` outside the vendored files, nor either browser extension, reads the cookie. - **Why it matters:** the threat model is the document operators and reviewers are told to trust; a line in it that is not true makes the rest harder to believe. Setting the flag costs nothing here, which is also why it is not a vulnerability: Django's own documentation notes that it adds no real protection when the token is in the page. - **Fix:** either set `CSRF_COOKIE_HTTPONLY = True` in `config/settings/base.py` beside `SESSION_COOKIE_HTTPONLY` (safe, since `app.js` takes the token from the form), or reword the threat model to "the session cookie is `HttpOnly`; every cookie is `Secure`". The first keeps the sentence true as written. ## Done when - `tests/security/test_requests.py` asserts `CSRF_COOKIE_HTTPONLY` alongside the existing `SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE` check at line 189 (fails today), or the threat model no longer claims it.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:29 +00:00
Author
Owner

Fixed in ffb060788 on main.

Fixed in ffb060788 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#538
No description provided.