Skip to main content

Self-Hosted Threat Model

Chaos Cypher's all-in-one container is designed for single-user self-hosted deployment — typically a homelab, personal LAN, or a single VM behind a reverse proxy you control.

This page lists the threats Chaos Cypher defends against, the threats accepted by design, and how to harden further if your deployment shape diverges from the defaults.

What Chaos Cypher Defends Against

  • Auth bypass via direct Cortex hits. Cortex refuses any X-Auth-User header that doesn't come paired with the deployment-local edge-auth token. Direct port-8080 access (in dev compose, behind a misconfigured proxy, etc.) returns 401.
  • Session theft via stale cookies after logout. Logout bumps session_epoch, invalidating every outstanding cookie for that user.
  • Prompt-injection-driven secret exfiltration. Model Context Protocol (MCP) file ingest is sandboxed to /data/mcp_uploads; the LLM cannot read /data/.queue_password, /data/.session_secret, or /data/.edge_auth_token. Hidden files (dotfiles) are explicitly rejected even inside the sandbox.
  • Container blast radius. Cortex and Neuron run as appuser with no-new-privileges. The supervisor password is held only by supervisord (not propagated into the Cortex/Neuron environment). Static nginx config lives in root-owned /etc/nginx/conf.d; only the runtime token fragment lives in an appuser-writable dir.
  • Common archive attacks. Backup, package, and source-loader archive extraction enforces path containment (is_relative_to), per-file + total decompressed size caps, member-count caps, and rejects symlinks and devices. Both the source-loader path and the .ccx package import path use the same ArchiveExtractor helper.
  • Credential leaks via diagnostic exports. Log files are scrubbed for Authorization, api_key=, Bearer, and token= patterns before zipping. Secret-bearing settings render as "configured" rather than partial reveals.
  • dev_mode shipping to production by accident. Cortex refuses to start when settings.dev_mode=True unless the operator has explicitly set CHAOSCYPHER_ALLOW_DEV_MODE=1 to acknowledge.

What Chaos Cypher Explicitly Doesn't Defend Against

These are pragmatic deferrals for the single-user self-hosted posture. The enterprise build addresses each.

ThreatWhy it is acceptedIf you need protection
First-arrival admin race.Open-by-default bind matches the self-hosted convention (Vaultwarden, Jellyfin, Home Assistant, Gitea). Adding a setup-token UX or loopback gate would push the "everyone disables the security" failure mode.Set CHAOSCYPHER_BIND=127.0.0.1 for first boot, complete /setup, then flip back. Or run the first boot on a network you fully control (laptop on home Wi-Fi, not a public VPS) before exposing it.
Online password brute-force beyond bcrypt + nginx rate-limit.Account lockout would lock the owner out. Self-hosted single-user attacker doesn't get many guesses through 5r/s + bcrypt.Set a 16+ char password; put the box behind a VPN or Tailscale; don't expose 0.0.0.0 without TLS.
DNS rebinding on operator-supplied URLs.The operator triggers POST /api/v1/sources/url themselves; an attacker needs DNS control + the operator fetching their URL.Don't fetch URLs from untrusted sources.
Tampered backup restores.The operator only restores backups they made themselves.Verify backup file integrity out-of-band (e.g., compare sha256sum to a copy you trust).
Disguised file uploads (e.g. SVG-with-script labeled as PDF).The operator uploads their own files.Don't upload files you didn't create.
Username enumeration via login timing.The operator knows the username.N/A.
Per-session cookie revocation list.Logout invalidates all sessions; the single-user model treats "log out" as "log out everywhere."Use a different account for short-lived shared access.
Windows file ACLs on .session_secret.Windows is a development target only.Run on Linux for any non-dev deployment.
HTTP/2 / HSTS preload.Both only matter once TLS is on; the operator opts into TLS.Enable TLS in Settings, then run a Mozilla Observatory check and tune the headers.

Hardening checklist for LAN / public exposure

If you set CHAOSCYPHER_BIND=0.0.0.0 (the default) and expose the box beyond your local machine:

  1. Set TLS first. Generate / install certs and switch to nginx-https.conf before opening the port.
  2. Set a 16+ char password at /setup (the minimum is 8 — go higher for any non-loopback exposure).
  3. Set CHAOSCYPHER_ALLOWED_HOSTS to your hostname/IP — blocks DNS-rebinding probes and Host-header spoofing.
  4. Put the box behind a VPN, SSH tunnel, or Tailscale if possible. The box is designed to be safe on a small trusted network, not on the open internet.
  5. First-boot on a private network. Either set CHAOSCYPHER_BIND=127.0.0.1 for the initial /setup (then flip to 0.0.0.0), or do the first boot on a trusted network where you'll be the first to reach the API.
  6. Restrict /api/v1/health to your monitoring source with a dedicated nginx allow … ; deny all; block if you don't want LAN scanners to fingerprint your stack. (Note: even unauthenticated /health only returns {status} after the E1 hardening — the detailed payload requires auth.)
  7. Rotate the API keys you've minted (/api/v1/auth/keys) on a cadence that matches your threat model.

Diagnosing auth misconfiguration

If nginx's auth_request is misconfigured, X-Auth-User may not arrive at Cortex, producing silent 401 storms. Hit GET /api/v1/health/auth (no auth required) for diagnostics:

{
"x_auth_user_present": false,
"recent_failed_attempts": 142,
"last_failure_at": "2026-05-06T18:32:11.000000+00:00"
}

When recent_failed_attempts is non-zero on requests that should be authenticated, the nginx auth_request forward isn't reaching Cortex. Check your nginx logs and verify the auth_request directive points to the correct upstream and that the edge-auth token is set.

Note: x_auth_user_present reflects the current request only — hit the endpoint while also making an authenticated API call to compare the two states.

Reporting issues

Security issues: see SECURITY.md in the repo root.

See also