# Security notes

**Controls in place (each is covered by a unit/integration test or was exercised live)**

- Authorization is enforced in services, not in the UI. `can()` is pure and unit-tested; every service loads the resource from the database, authorizes against its real tenant, and returns 404 for foreign or missing ids.
- Sessions identify the user only; status, session version and grants are re-read from the database on each request. Suspension and role changes apply immediately.
- Login: bcrypt (cost 12), per-account lockout (DB-backed), per-IP limiter, uniform error message, audit of failures.
- Client IPs are trusted only when `TRUST_PROXY=true`; the right-most `X-Forwarded-For` entry is used so spoofed left-hand values do not matter. IPs on form submissions are stored only as keyed HMAC hashes.
- Server actions return typed results and never leak internal errors; unexpected errors are logged by type/message only.
- Uploads: content-sniffed type allowlist, size and storage quota limits, server-chosen storage names, `nosniff`, sandboxed responses; private assets need a signed token or an authorized session.
- Public forms: signed render token, honeypot, minimum fill time, link-spam heuristic, per-form and per-IP rate limits, same-origin and host-ownership checks, JSON-only, 64 KB cap. Submissions are never written to logs.
- Redirect, link and navigation targets reject `javascript:`, `data:`, scheme-relative and credentialed URLs.
- CSV exports neutralise spreadsheet formulas and are audited by row count only.
- Draft previews use HMAC tokens (purpose-bound, page-bound, 24 h) and are framed same-origin only.
- Audit metadata is redacted by key; audit failures never break a request.
- Cron endpoints are off unless `CRON_SECRET` is set and use constant-time comparison.

**Known limits:** no CSP, no MFA, no email-based password reset (admins can reset a password; users can change their own), no forced password change on first sign-in, in-memory rate limiting, no malware scanning of uploads, no domain-ownership verification, no email transport. See `PROGRESS.md`.
