Security model
Sessions, CSP, secrets names-only, scopes, and operator hardening practices.
Trust boundary
Authorization runs on the Cloudflare Worker. The SPA and CLI are clients only. If a client can call an API without a server-side check, that is a bug — not an operator workaround. Domain commands and middleware enforce roles and scopes on every mutation.
Session cookies
Human sessions use the speakerops_session cookie with HttpOnly, Secure, SameSite=Lax, Path=/, and bounded Max-Age. Magic-link tokens are single-use and short-lived. Never place auth material in localStorage or sessionStorage.
- HttpOnly + Secure + SameSite=Lax on production sessions
- No auth secrets in frontend storage APIs
- Redact cookies, tokens, and magic links in logs
Content-Security-Policy
Production CSP uses default-src 'self', tight script-src without unsafe-inline on Worker paths, Turnstile host allowances, frame-ancestors none, and companion headers such as X-Content-Type-Options and Referrer-Policy. Dev servers may relax script-src for Vite HMR only — never copy that relaxation to the Worker.
Secrets discipline
Documentation and commits may list environment variable names only. Values live in wrangler secrets, local gitignored files, or a mode-600 secrets channel. CLI keys use SPEAKEROPS_API_KEY as the name; the value is the spk_ secret minted once.
| Surface | Rule |
|---|---|
| Git | Names only; no tokens, keys, or magic links |
| Logs | Structured logs; redact secrets |
| API keys | Hash at rest; secret once on create |
| Default-deny scopes | comms:send, decisions:write, keys:admin |
Operator hardening checklist
Keep admin allowlists tight. Revoke unused API keys. Prefer least privilege per agent. Review audit events after bulk decisions or sends. Confirm Turnstile on public CFP. Confirm demo file-upload limits remain intentional.