Security
Security posture for Sovereign AI deployments operated by Twentyfirst Media Group. Two applications: control (Twentyfirst-hosted) and workspace (client-hosted). This document summarizes controls and configuration — not a formal penetration-test report.
Security principles
- Data sovereignty — chat, documents, and embeddings stay in the client workspace database; control receives aggregates only (usage, health, support packs).
- Model boundary — inference runs outside the apps; prompts are sent only to configured endpoints.
- Evidence trail — audit logs and usage events on workspace; operator audit on control.
- Least privilege — control operators vs workspace tenant admins vs members.
- Production hardening — distributed rate limits (workspace) and secrets management for internet-facing production.
Authentication and sessions
- NextAuth v5 with JWT sessions (30-day max age, configurable in
src/auth.ts). - Passwords hashed with bcrypt; stored in
users.password_hash. - Optional Google and Microsoft Entra ID when
AUTH_GOOGLE_*/AUTH_MICROSOFT_*are set. - Session secret:
NEXTAUTH_SECRET(minimum 32 characters, validated at startup). trustHost: true— required behind reverse proxies; ensure the proxy strips client-suppliedHostspoofing.
Sign-in observability
Credentials sign-ins trigger:
writeAuditLog— actionlogin, resourcesession, with IP and user-agent fromgetRequestAuditContext()logUsageEvent— typelogin, scoped to resolved tenant (or first membership)
OAuth sign-ins use the Drizzle adapter; extend the signIn callback similarly if client policy requires parity.
Authorization
| Layer | Mechanism |
|---|---|
| Route / UI | Session + role checks (assertTenantAdmin, membership guards) |
| API | auth() + getWorkspaceContext() + membership match |
| Database (optional) | Row Level Security via drizzle/rls.sql and withTenantRls() |
Control operators use the control app (/ops); workspace tenant admins use /admin; chat requires member (or higher).
Network and transport
- TLS terminates at the load balancer or ingress — never expose plain HTTP to the public internet.
- Forward
X-Forwarded-For/X-Real-IPfor accurate audit and rate-limit keys. - Restrict database and Redis to private networks;
DATABASE_URLmust not be reachable from the client browser. - Model servers should sit in the same trust zone as the app or in an explicitly approved cross-zone path.
Rate limiting
| Endpoint class | Default (per minute) | Env override |
|---|---|---|
| Auth | 20 | RATE_LIMIT_AUTH_PER_MINUTE |
| Chat | 60 | RATE_LIMIT_CHAT_PER_MINUTE |
| Upload | 15 | RATE_LIMIT_UPLOAD_PER_MINUTE |
Production uses local Redis over TCP (REDIS_URL, default redis://127.0.0.1:6379). Chat, upload, and auth limits share that store via ioredis + rate-limiter-flexible. If Redis is unreachable, limits fall back to process-local memory (ineffective under horizontal scale).
Upload policy
MAX_UPLOAD_BYTEScaps request body size (default 25 MB).- MIME and extension checks in
src/lib/upload-policy.ts. - Files stored under tenant-scoped paths in local or S3 storage — never served as executable content from the app origin.
Secrets management
| Secret | Handling |
|---|---|
NEXTAUTH_SECRET | Platform vault; rotate with forced re-login |
DATABASE_URL | IAM/network-restricted; no commit to git |
MODEL_API_KEY | Per-tenant profile or env; client-owned keys preferred |
AUTH_*_SECRET | OAuth provider consoles; rotate on staff change |
CRON_SECRET | Bearer token for /api/cron/* |
SMTP_PASSWORD | Vault; TLS to SMTP relay |
Never commit .env. Use .env.example as documentation only. CI builds use SKIP_ENV_VALIDATION=1 with dummy values — not for production runtime.
Headers and middleware
middleware.ts applies request IDs and locale routing before page handlers (it does not run on /api). Auth, chat, and upload rate limits run in Node route handlers against local Redis. Review when adding new public routes.
Audit and logging
- Audit logs (
audit_logs) — operator actions, logins, configurable diffs. - Usage events (
usage_events) — chat, embeddings, logins, uploads. - Application logs — structured JSON in production (
src/lib/logger.ts); includex-request-idwhen correlating.
Retain logs per client contract (GDPR, industry regulation). PII in logs should be minimized — user IDs over email where possible.
Data protection (GDPR-oriented)
- Data controller — typically the client organization; Twentyfirst Media Group acts as processor per DPA.
- Sub-processors — document model providers, cloud host, object storage in the client annex.
- Right to erasure — procedure: delete user, memberships, threads/messages, uploaded files, and anonymize audit references as legally permitted.
- Cross-border transfer — use EU regions for Postgres, storage, and models when contract requires.
Optional database RLS
Apply drizzle/rls.sql for defense-in-depth. Application code must set app.tenant_id inside transactions (withTenantRls). RLS is not a substitute for API authorization but limits blast radius of query bugs.
Vulnerability management
- Dependabot /
npm auditon the repository; patch critical issues before client production windows. - GitHub Actions CI runs
lintandbuildon every PR. - Client-specific pen tests scheduled per SOW.
Incident response (summary)
- Contain — suspend tenant, rotate compromised secrets, block abusive IPs at WAF.
- Assess — audit logs, usage spikes, model endpoint anomalies.
- Notify — client DPO / security contact per contract timelines.
- Recover — restore from backup, redeploy patched image, re-verify with
npm run smoke. - Review — post-incident notes; update runbooks and this document if gaps found.
Security contacts
Production incidents for managed clients: use the client-specific escalation path in the delivery runbook. Do not post credentials, tokens, or tenant exports in public channels.
For security findings in the upstream product codebase, contact Twentyfirst Media Group through the authorized maintainer channel defined in the license agreement.