Security and threat model
Assets
xAI API key (spend), the studio (publication rights), private prompts and drafts, visitor privacy.
Controls
| Threat | Control |
|---|---|
| Key leaks to the browser, logs or API | Key read only in src/lib/env.ts (server); never serialised; structured logger redacts authorization and anything matching xai-…; scripts/secrets-scan.ts scans client chunks and tracked files in pnpm check |
| Studio takeover | No default password: scripts/bootstrap-owner.ts generates a one-time password (scrypt hash stored), forced change at first login; TOTP second factor (enforced once enrolled); sessions are random 256-bit tokens stored hashed, HttpOnly; Secure; SameSite=Strict, 12 h idle / 7 d absolute; login rate limit 5/min/IP + 10/h/account |
| Privilege misuse | Roles owner > editor > viewer; every studio page, server action and API route calls requireRole() server-side; all mutations are written to audit_log |
| CSRF | SameSite=Strict cookie + Origin check on every mutating request; Server Actions are origin-checked by Next.js |
| XSS | React escaping, no dangerouslySetInnerHTML on user or model text, CSP (default-src 'self', no remote scripts, frame-ancestors 'none') |
| Runaway spend | Budget gate before every paid call, kill switch, bounded retries, regeneration cap, scheduler quota, idempotency keys (no duplicate paid generation), circuit breaker |
| SSRF | The server fetches only api.x.ai and provider result URLs, which are validated against an allow-list of xAI hosts (src/providers/http.ts). No user-supplied URL is ever fetched |
| Upload abuse | The studio accepts no file uploads in this version (all media is generated); nothing to scan. Reference uploads, if added, must validate MIME/size and run a scanner |
| Private media exposure | /media/* serves only assets linked to a published film; everything else needs a studio session or a signed URL (HMAC-SHA256, expiry) |
| Abuse of public write endpoints | Reactions, analytics and reports are rate-limited per hashed visitor and accept fixed-shape payloads only |
| Injection | Parameterised SQL (postgres-js tagged templates) everywhere |
Content safety and provenance
- Every film carries a visible "AI-generated" disclosure, a public provenance block (models, dates, pipeline
stages, human approval) and container metadata in the MP4 (
comment,description). C2PA signing is not implemented (no signing certificate); the provenance JSON is served at/api/films/:slug. - No impersonation: the brief validator and the Quality Critic reject real, named people; voices are provider presets only (no cloning).
- Factual series must pass the Research Editor: each claim needs a source, otherwise the film is labelled fiction/speculation or held for a human.
- Moderation happens before publication (provider moderation flag + critic safety score + human approval).
/reportfiles a takedown request; an operator can unpublish in one action (assets stop being served).
Visitor data
No account, no third-party script, no advertising identifier. A visitor is a daily-rotating hash
(sha256(secret | day | ip | user-agent)), used only to de-duplicate views and likes; raw IPs are not stored.
Analytics events are deleted after 90 days; bookmarks live in the browser (localStorage).
Known gaps
Passkeys (WebAuthn) are not implemented (password + TOTP instead). C2PA not implemented. No external error tracker (structured logs + PM2 + health probe). Docker Compose file is provided but the production deployment is PM2 on macOS.