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.

SurfaceRule
GitNames only; no tokens, keys, or magic links
LogsStructured logs; redact secrets
API keysHash at rest; secret once on create
Default-deny scopescomms: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.