> ## Documentation Index
> Fetch the complete documentation index at: https://docs.try-airlock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Policy files, the egress allowlist, mounts, env passthrough, and backend settings.

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.

<Note>
  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).
</Note>

Create a starter file with `airlock init`:

```toml .airlock.toml theme={null}
[network]
allow = ["registry.npmjs.org"]

[env]
passthrough = ["MY_EXTRA_KEY"]

[mounts]
readonly = ["/Users/me/reference-docs:/mnt/docs"]
```

<Note>
  Airlock never touches your repo's `.gitignore`.
  No credentials are stored in it; those live in `~/.airlock/secrets.toml`.
</Note>

***

## \[network]

<ParamField path="mode" type="string" default="allowlist">
  `allowlist` — only listed hosts are reachable. `blocklist` — everything is
  reachable except listed hosts.
</ParamField>

<ParamField path="allow" type="string[]">
  Hostnames the sandbox may reach, in `allowlist` mode.
</ParamField>

<ParamField path="block" type="string[]">
  Hostnames refused in `blocklist` mode.
</ParamField>

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.

```toml .airlock.toml theme={null}
# Open by default: don't slow the team down, but keep the record and
# close the obvious exfil routes.
[network]
mode  = "blocklist"
block = ["pastebin.com", "gist.github.com", "file.io", "transfer.sh"]
```

**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.

<Note>
  `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.
</Note>

### Allowlist mode

**Allowed by default:**

| Host                | Why                         |
| ------------------- | --------------------------- |
| `api.anthropic.com` | Claude API                  |
| `claude.com`        | Claude Code auth and config |
| `claude.ai`         | OAuth login flow            |
| `api.openai.com`    | OpenAI API                  |
| `openrouter.ai`     | OpenRouter API              |
| `api.x.ai`          | xAI API                     |

Enabled [MCP connectors](/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:

```toml ~/.airlock/policy.toml theme={null}
[network]
allow = ["statsig.anthropic.com"]
```

## \[env]

<ParamField path="passthrough" type="string[]">
  Host environment variables copied into the sandbox, if set on the host.
</ParamField>

**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.

<Note>
  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.
</Note>

## \[mounts]

<ParamField path="readonly" type="string[]">
  Extra read-only mounts, in `hostpath:containerpath` form.
</ParamField>

```toml theme={null}
[mounts]
readonly = ["/Users/me/reference-docs:/mnt/docs"]
```

Mounted with `:ro` appended, so the agent can read them but not write them.

## \[image]

<ParamField path="sandbox" type="string" default="airlock/base:latest">
  Image used for the sandbox container.
</ParamField>

<ParamField path="proxy" type="string" default="airlock/proxy:latest">
  Image used for the egress proxy container.
</ParamField>

Unlike the lists, these replace the inherited value — useful for pinning a tag
or testing a locally built image.

## \[mcp]

<ParamField path="enable" type="string[]">
  MCP connectors enabled for this repo, by name.
</ParamField>

```toml theme={null}
[mcp]
enable = ["notion"]
```

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`](/mcp-connectors):

```toml ~/.airlock/secrets.toml theme={null}
[notion]
token = "..."

[confluence]
site  = "acme"
email = "you@acme.com"
token = "..."
```

* **`~/.airlock/auth.json`** (mode `0600`) — your identity, written by
  [`airlock login`](/cli#airlock-login): the backend URL and an
  ingest-scoped API token.

<Warning>
  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](/deploy#accounts-and-tokens).
</Warning>

## Where things live

| Path                                | Contents                                            |
| ----------------------------------- | --------------------------------------------------- |
| `~/.airlock/policy.toml`            | Global policy                                       |
| `.airlock.toml`                     | Per-repo policy, in the repo root                   |
| `~/.airlock/secrets.toml`           | Connector credentials, mode `0600`                  |
| `~/.airlock/auth.json`              | Your identity: backend URL + API token, mode `0600` |
| `~/.airlock/logs/<repo>/`           | Egress audit logs, one file per day                 |
| `~/.airlock/agent/`                 | The sandbox agent's own home, isolated from yours   |
| `~/.airlock/agent/claude/projects/` | Session transcripts, a source for `airlock publish` |
| `.mcp.json`                         | MCP server registrations, committed with the repo   |
