arizuko › security
arizuko treats every agent run as untrusted code. Four enforcement layers stand between the model and the network: per-group egress allowlist at an HTTP/HTTPS proxy, secrets that never enter the container, selective TLS termination for placeholder substitution, and a DNS resolver that returns NXDOMAIN for anything off the allowlist.
The agent is the threat surface. Claude Code runs with bypassPermissions inside its container and can shell out, fetch URLs, run generated scripts — under normal operation. Under prompt injection, RCE in a tool call, or an adversarial skill, that same scope is what the attacker gets. The platform assumes this and enforces around it.
The system surface (operator, gateway daemons, store) sits outside the agent boundary. It trusts the operator, the chat-platform adapter ingress signed by CHANNEL_SECRET, and the user_groups ACL resolved through auth.MatchGroups. Inbound web traffic is OAuth-gated at proxyd; identity headers are HMAC-signed and verified by every backend via auth/middleware.go.
The full trust-zone diagram and per-boundary mechanism table is in SECURITY.md in the repo. Per-daemon files ship next to the source when a daemon's threat model outgrows a row (e.g. ipc/SECURITY.md for the MCP socket and SO_PEERCRED check).
Every agent container attaches to a per-group Docker network created with internal: true — no default route to the internet. The only path out is the crackbox forward proxy, set as HTTPS_PROXY in the container's environment at spawn. Non-cooperating clients fail closed: a process that ignores HTTPS_PROXY finds the network unrouted.
Connections to non-allowlisted hosts are denied silently at the proxy. Connections to allowlisted hosts are CONNECT-tunneled (HTTPS) or forwarded (HTTP). The default seed is anthropic.com and api.anthropic.com (store/migrations/0037-network-rules.sql). Operators extend per folder:
arizuko network <instance> allow <folder> api.github.com
Allowlist resolution walks the folder ancestry and dedupes (store/network.go ResolveAllowlist). The proxy itself, packaging, and CLI live in the crackbox component page; the same daemon ships standalone for non-arizuko uses (CI scripts, vendor binaries).
The container holds placeholder strings, never credentials. Real values live only in the proxy. At spawn, gated resolves the (folder, caller-user) secrets overlay into two maps: a flat {env_name: placeholder} map for container env, and a {placeholder: {value, header, domains}} map registered with the proxy alongside the allowlist (specs/9/11-crackbox-secrets.md).
The agent uses placeholders verbatim in outbound HTTP headers. The proxy terminates TLS for the destinations registered with secrets, replaces the placeholder with the real value, and forwards. Two consequences:
sk-ant-PLACEHOLDER-anthropic_a3f9b21c); they carry no user identity.Two scopes compose at spawn: folder-scoped (operator-managed, team API keys) and per-user overlay (user-managed via /dash/me/secrets). Per-user values override folder values for the same env name. At rest: AES-GCM under AUTH_SECRET in the secrets table (store/migrations/0034-secrets.sql).
Status: secrets table and folder-scope CLI shipped. Proxy-side substitution and per-user dashboard CRUD spec'd, implementation milestones M0–M6 listed in the spec.
The proxy bundles a UDP/53 listener. The container's resolver points at it via docker create --dns <proxy-ip>. Per query:
1.1.1.1:53); reply validated against source addr + ID + question, then relayed.QTYPE=ANY → REFUSED regardless of allowlist (no amplification reflector).The HTTP/CONNECT enforcement is still the primary gate; DNS is additive. If a client somehow ignores both the proxy env vars and resolv.conf, the internal: true Docker network ends the conversation. Spec: specs/9/15-crackbox-dns-filter.md. Implementation: crackbox/pkg/dns/.
Status: crackbox-side DNS server shipped. arizuko-side container plumbing (passing --dns with the resolved per-folder crackbox IP) tracked as a follow-up under spec 9/10.
Every request from outside the docker network passes through proxyd. It terminates TLS, verifies the OAuth-issued JWT (refresh-token cookie fallback), and signs identity headers with an HMAC shared with the backends:
X-User-Sub: google:alice
X-User-Name: Alice
X-User-Groups: solo/inbox,corp/eng/sre
X-User-Sig: <HMAC over the above + timestamp>
Every backend verifies the signature via auth/middleware.go: RequireSigned (strict, redirect on fail) on always-authed routes; StripUnsigned (scrub spoofed and continue) on mixed public/authed routes like onbod's invite landing. The shared key is PROXYD_HMAC_SECRET; if unset, proxyd generates an ephemeral one per run and webd will reject everything — covered in SECURITY.md § Identity header trust.
One OAuth login covers GitHub, Google, Discord, and Telegram — account linking unifies them under one auth_users.sub. Per-route auth modes (public / user / operator) are declared per-daemon in service config and aggregated into PROXYD_ROUTES_JSON at compose generation (specs/6/2-proxyd-standalone.md). Public paths today: /pub/*, /health, /slink/* (token-bound, rate-limited 10/min/IP).
Adapter ingress is a separate trust boundary — channel adapters reach gated over the internal docker network only, authenticated by Authorization: Bearer $CHANNEL_SECRET (chanlib/chanlib.go). proxyd does not see channel traffic; channels do not see web traffic.
The destination: every security configuration (secrets, allowlists, grants, routes, invites) is reachable two ways, sharing one handler per resource.
/v1/<resource>, OAuth-gated through proxyd. Each daemon owns its tables and exposes them; dashd aggregates the UI on top.Operations spawning new state, mutating ACLs, or reading audit data are operator-only (tier 0). Operations scoped to a group's own files, members, and tasks are inner-group (tier 1+). The per-daemon ownership table is in specs/6/R-platform-api.md.
Status: REST surface federated per-daemon, partial coverage today (dashd HTML reads + webd /api/*; gated/timed/onbod /v1/* in flight). MCP surface comprehensive on the inside (31+ tools, tier-gated). Uniform principle — same handler under both surfaces — being specced.
specs/9/12-crackbox-sandboxing.md; opt-in, not default.AUTH_SECRET. The rest of /srv/data/<instance>/ is plain files; back it up and protect the host accordingly.SECURITY.md — full trust model, boundary table, identity-header signing, trust-zone diagram, incident logspecs/9/ — security spec bucket: standalone crackbox (9), arizuko integration (10), secrets injection (11), sandboxing options (12), DNS filter (15)specs/6/2-proxyd-standalone.md — per-route auth modes, federated platform APIspecs/6/1-auth-standalone.md — auth as a library: mint, verify, downscope, MCP tool surfaceipc/SECURITY.md — MCP unix socket, SO_PEERCRED peer-uid check, per-group mount isolation