Skip to main content
Airlock resolves policy from three layers, in order:
  1. Built-in defaults — model APIs and agent keys, so a fresh install works.
  2. ~/.airlock/policy.toml — your global policy, applied to every repo.
  3. .airlock.toml — per-repo overrides, read from the current directory.
Lists append; image names override. Each layer adds to network.allow, env.passthrough, mounts.readonly, and mcp.enable. Only image.sandbox and image.proxy replace the previous value.
Because lists only append, a policy file cannot remove a built-in default. Which is why the defaults are kept minimal. Claude Code telemetry (statsig.anthropic.com) is not a default: the shipped allowlist is dark, and telemetry is an explicit opt-in (see below).
Create a starter file with airlock init:
.airlock.toml
Airlock never touches your repo’s .gitignore. No credentials are stored in it; those live in ~/.airlock/secrets.toml.

[network]

string
default:"allowlist"
allowlist — only listed hosts are reachable. blocklist — everything is reachable except listed hosts.
string[]
Hostnames the sandbox may reach, in allowlist mode.
string[]
Hostnames refused in blocklist mode.
A bare domain also covers its subdomains — openrouter.ai matches api.openrouter.ai. Matching is case-insensitive, and a refusal is a 403 in either mode.

Choosing a mode

Both modes log identically: every request, allowed or refused, with host, path, byte counts, and duration. The mode changes what is refused, never what is recorded. Observability does not depend on being strict.
.airlock.toml
Pick allowlist for repos touching sensitive systems, credentials, or customer data — an unknown host is refused before it can matter. Pick blocklist for ordinary development, where the cost of a blocked package registry exceeds the risk of the request. Mode is per-repo policy, so one team can run open while another runs strict.
blocklist mode does not weaken the boundary that makes egress observable at all: the sandbox still has no route out except the proxy, so every request is still attributed and logged. It widens what the proxy permits, not what it sees.

Allowlist mode

Allowed by default: Enabled MCP connectors add their own domains on top of this. Telemetry is opt-in. Claude Code’s telemetry host is deliberately absent from the defaults, so nothing leaves the sandbox that you did not allow. To opt in, add it yourself:
~/.airlock/policy.toml

[env]

string[]
Host environment variables copied into the sandbox, if set on the host.
Passed through by default: ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, XAI_API_KEY. A variable that is not set on the host is silently skipped.
Airlock itself sets AIRLOCK_REPO, AIRLOCK_USER_ID, and AIRLOCK_USER_EMAIL in the sandbox and proxy containers — ids only, used for event attribution. Tokens never enter a container.

[mounts]

string[]
Extra read-only mounts, in hostpath:containerpath form.
Mounted with :ro appended, so the agent can read them but not write them.

[image]

string
default:"airlock/base:latest"
Image used for the sandbox container.
string
default:"airlock/proxy:latest"
Image used for the egress proxy container.
Unlike the lists, these replace the inherited value — useful for pinning a tag or testing a locally built image.

[mcp]

string[]
MCP connectors enabled for this repo, by name.
Normally written for you by airlock setup mcp. Enabling a connector widens the egress allowlist with its domains and injects its credentials from ~/.airlock/secrets.toml. An unknown name here is a hard error at startup.

Secrets

Credentials never live in a repo or in a policy file. Identity is kept apart from connector credentials, because they are different trust domains:
  • ~/.airlock/secrets.toml (mode 0600) — connector credentials, keyed by section, written by airlock setup mcp:
~/.airlock/secrets.toml
  • ~/.airlock/auth.json (mode 0600) — your identity, written by airlock login: the backend URL and an ingest-scoped API token.
The API token is bound server-side to your account and only allows ingest under that identity. It cannot read anyone’s timeline and is used only by host-side commands: never injected into a sandbox and never written into a repo. Console access is a separate login with its own session; there is no shared read-everything key. See deploy.

Where things live