← Back

Patch notes — security hardening (stored XSS, mobile IDOR, headers) (2026-06-18)

Branch: dev (not yet merged to main, so not deployed — main is the prod trigger). Six fixes from an authorized security test of the admin app, driven against a local full stack on localhost:8080. Full write-up: ../Bugfix/bugfix-report-2026-06-18-security-hardening.md. Docker smoke 157/0 on the dev tree.

1 — Avatar stored XSS → admin takeover (Critical) ✅

The /account avatar accepted any data:image/…-prefixed string and rendered it raw into a style="…url('…')" attribute shown cross-user (an author’s avatar on the admin’s ticket thread) — a customer could store a payload that ran in the admin’s browser (confirmed live). Now validated as a strict base64 image data URL at the store and esc()-escaped at every render sink. Commit 429b5d3.

2 — Mobile ticket IDOR (Critical) ✅

/mobile/ticket let a customer read any ticket in their company, dropping the company_admin gate the desktop view enforces. Routed through the shared _customer_ticket() boundary. Commit 5ef94bc.

3 — Username stored XSS on the admin Users page (High) ✅

User creation now constrains usernames to [A-Za-z0-9._-]{1,64} (they’re interpolated into form="u_<name>" markup; a company_admin customer could reach one of the creation paths). Commit 429b5d3.

X-Content-Type-Options: nosniff on every response; X-Frame-Options: SAMEORIGIN + Referrer-Policy on HTML; session cookie marked Secure on HTTPS (via X-Forwarded-Proto from the Traefik edge; unset on plain HTTP so dev/smoke still work). Commit da7730e.

5 — Constant-time collector token (Low) ✅

/api/collector/push now compares the token with hmac.compare_digest. Commit 4602f70.


Deferred (tracked): SSRF guard for monitor/api-source/deployment URLs (High; needs a shared assert_public_url() + moving those routes into ADMIN_ONLY_PATHS); invoice company-scoping (Medium; matches the documented v1 “staff = all companies” model). No access control was weakened. Files changed: ethica/handlers/{account,mobile,public,settings,base,auth}.py, ethica/ui/html.py.