PST-058: Authenticated HTML pages carry no Cache-Control: no-store, so Back after sign-out can restore personal data on a shared browser #787
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#787
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-058. Severity: Low.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/config/settings/base.py(MIDDLEWARE list)Who can do it, and what they need. The next person at a shared or borrowed computer (library, family machine, co-working desk). Precondition: the previous person used Postulo in that browser and signed out but did not close the browser window/tab history; no credentials are needed.
How it works.
Cache-Control: no-store, so it is eligible for the browser's back/forward cache (Firefox and Safari restore it verbatim; Chrome restores it unless the page was no-store); the page is shown from memory without any request reaching the server, including the address, applications, companies and reminders it held.pageshowhandlers exist precisely because pages come back this way ("Coming back to a page from the cache with yesterday's dirty mark on it"). Files already carryprivate, max-age=0, no-store; HTML pages carry nothing.Impact. Disclosure of the previous person's personal data (the threat model's sharpest item, the home address on Your details, plus the whole job-search record) to whoever next uses the browser, without a session. Limited scope: physical access to the same browser profile, and only the pages visited before sign-out.
Evidence.
Verification (verdict: confirmed; severity after verification: Low; first-pass claim: Medium). Lower from medium to low. Correct the mechanism: only the browser's back/forward cache restores the stale page, the HTTP cache does not (Vary: Cookie plus the deleted sessionid forces a refetch and a redirect to sign-in). Precondition: physical access to the same open tab or window shortly after sign-out (bfcache entries expire, in Chrome after about ten minutes). The attacker gets a read-only DOM snapshot of pages the previous user viewed in that tab (Your details with address, phone and date of birth, the applications table) and cannot act on them. Location: src/postulo/config/settings/base.py MIDDLEWARE at lines 93-122 (no cache-header middleware) and src/postulo/templates/base.html; app.js pageshow handlers (lines 1668, 1934, 2131, 3887) do not reload on event.persisted. Remediation: add a small middleware or after-login decorator setting 'Cache-Control: no-store' (optionally 'private, max-age=0') on every authenticated HTML response, and/or send 'Clear-Site-Data: "cache"' on logout, and in app.js reload or redirect on pageshow with event.persisted when the session is gone. …
Suggested remediation. Add a small middleware (or wrap the base view mixin) that calls Django's
add_never_cache_headers()/ setsCache-Control: private, no-storeon every response to an authenticated request whose content type is HTML (and on the sign-in/sign-out responses).no-storemakes the page ineligible for the back/forward cache in Firefox and Safari and, in Chrome, evicts it when the session cookie changes at sign-out. Optionally sendClear-Site-Data: "cache"on the sign-out response. …Fixed in
90c75b28fon main.