Security

Security

Claude Code sandbox

Claude Code runs inside sandbox-exec-claude, a purpose-built bwrap container with stricter isolation than the regular terminal. Every session gets its own sandboxed environment — file system, network, process tree, and resource limits are all independent.

  • Filesystem — bwrap builds a read-only tmpfs root with exact per-file bind mounts. Only the specific binaries Claude Code needs (node, git, bash, coreutils) are visible. No other host paths exist inside the container
  • Process isolation — PID, IPC, and UTS namespaces unshared. The sandbox sees only its own 6–10 processes; the rest of the host is invisible
  • Capability drops — 27 Linux capabilities dropped, including CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_NET_ADMIN, CAP_NET_RAW, and more
  • Seccomp — optional BPF syscall filter blocks low-level syscalls not needed by Claude Code
  • Cgroup limits — 512 MiB RAM, 80% CPU, 64 PIDs per session. Runaway processes are automatically constrained
  • Env stripping — only safe variables are passed in. All server credentials (Supabase keys, Gitea tokens, DB URLs) are absent from the environment

Network isolation

Each Claude Code session runs in its own Linux network namespace with a dedicated veth pair. The sandbox has no default route — only a single /30 link to the LLM proxy on port 3000 is reachable. Gitea (:3001), Postgres (:5432), SSH (:22), and the public internet all return "Network unreachable" from inside.

  • Per-session veth pair (cc-h<sess> / cc-s<sess>) in the 10.202.x.x/30 range
  • Host-side iptables rules allow only the sandbox's IP to reach port 3000 — everything else on the veth interface is dropped
  • The LLM proxy validates the session token and routes API calls to the configured provider — credentials never touch the sandbox
  • Fallback: if the network namespace cannot be created, bwrap runs with --unshare-net instead — the session gets a networkless container rather than open host access
  • Verifiable from the host: ip netns list shows one namespace per active session; ip netns exec cc-ns-<sess> ip route confirms only the veth route exists

Session & credential hygiene

API session tokens, git credentials, and internal network addresses are kept out of tenant-visible storage.

  • Session tokens — each Claude Code session generates a random cc-sess- token used as the API key inside the sandbox. Tokens expire after 4 hours even if the connection drops without cleanup. A 30-minute sweep removes any leaked tokens from memory
  • Token visibilityANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, and ANTHROPIC_BASE_URL are in Claude Code's env.deny list. The model cannot read them via the Bash tool — only the Claude Code binary itself sees them
  • Git credentials — project .git/config files store only the credential-free remote URL (http://127.0.0.1:3001/user/repo.git). Credentials are injected transiently per git operation and never persisted to disk
  • Transcript scrubbing — internal veth IPs (10.202.x.x) are replaced with proxy.internal in conversation transcripts when the session closes, so internal network topology is not visible in the session home directory
NoteThe Claude Code session home (/srv/cli-home/cc-<id>) is bind-mounted into the sandbox and is therefore tenant-visible. No secrets, credentials, or internal addresses are written there.

Terminal sandbox

The in-browser terminal runs inside a bubblewrap (bwrap) sandbox. Your commands execute as an unprivileged user — filesystem writes outside your project directory are blocked, and sensitive server environment variables (Supabase keys, Gitea tokens, database URLs) are stripped before the shell starts.

  • All sensitive env vars are removed — only safe variables like PATH, HOME, and NODE_ENV are passed through
  • The npm cache is redirected to /tmp/npm-cache to prevent cross-project interference
  • Network access is allowed for package installs and API calls

Dev server isolation

Every project's dev server runs as a dedicated system user (devuser) with a separate group, isolated from other projects. Secrets are stripped from the dev server environment using the same allowlist as the terminal.

  • Dev servers never have access to Gitea tokens, Supabase service keys, or Cloudflare credentials
  • Each project gets its own port — dev servers cannot reach each other directly
  • Preview containers (published previews) use isolated nginx:alpine Docker containers per project

Database isolation

Every project gets its own PostgreSQL schema, completely isolated from other projects on the same server. Row-level access is enforced at the API layer:

  • ANON_KEY requests are blocked by default — you must explicitly enable public access per table
  • Logged-in users can only read, update, and delete their own rows (auth_select_own, auth_update_own, auth_delete_own are all on by default)
  • Users with role=admin bypass row-ownership filters and can access all rows
  • SQL queries via the console block COPY, GRANT/REVOKE, and system catalog access (pg_catalog) to prevent data exfiltration or privilege escalation
  • Cross-schema references are blocked — queries are scoped to your project's schema only
NoteThe DB_API_KEY bypasses all row-level policies and should only be used server-side (edge functions, AI commands). Never expose it in frontend code.