Config Reference
Airlock config is YAML. Everything lives in a single file (typically airlock.yaml). The config hot-reloads on save — no restart needed.
Top-level sections
providers: # MCP servers and built-ins
value_sets: # Reusable argument value lists
arg_dimensions: # Reusable tool-argument bindings for arg_scope
profiles: # Reusable permission sets
default_profile: # Optional profile every agent inherits (DRY low-risk grants)
sandbox_presets: # Reusable sandbox envelopes
clis: # CLI tools exposed as MCP tools
apis: # REST APIs exposed as MCP tools
agents: # Per-agent policy and config
approvals: # Global approval provider config
security: # Host blocking, domain allowlists
audit: # Audit log settings
server: # Gateway server settings
lint: # Static hygiene rule configurationproviders
Declares upstream tool sources.
providers:
# MCP server over stdio
github:
type: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-github']
env:
GITHUB_PERSONAL_ACCESS_TOKEN: '${GITHUB_TOKEN}'
# MCP server over SSE
remote:
type: sse
url: https://tools.example.com/sse
headers:
Authorization: 'Bearer ${TOKEN}'
# MCP server over streamable HTTP (with optional OAuth)
cloud:
type: http
url: https://mcp.example.com
oauth: true
client_id: ${CLIENT_ID}
client_secret: ${CLIENT_SECRET}
oauth_callback_port: 9876
# Built-ins
exec: builtin
http: builtin
airlock: builtinprofiles
Reusable permission sets. See Composable Profiles.
profiles:
readonly:
allow:
- '*/list*'
- '*/get*'
- http/get
developer:
extends: [readonly]
allow:
- github/*
- git/*
ask:
- github/create_prProfiles may extend other profiles. Profile inheritance is resolved once at config load, before agents consume profiles. Unknown profile references and profile cycles are fatal config errors. Profiles can also carry arg_policy and arg_scope entries that agents inherit alongside allow/ask/deny rules.
default_profile
Optional. Names one profile that every agent inherits automatically, so a low-risk grant you want everywhere (self-inspection tools, a shared notify channel) lives in one place instead of being copied onto each agent.
default_profile: base
profiles:
base:
allow:
- airlock/status
- airlock/list_provider_toolsIt is prepended to each agent's extends, so it resolves at lowest precedence — an agent can still deny (or ask-gate) anything the default grants (deny > ask > allow). Because the named profile may itself extends a chain, one entry can compose many. A referenced profile that does not exist is a fatal config error. Opt a single agent out with inherit_default: false (see agents).
value_sets and arg_dimensions
Reusable argument restrictions are split into value sets and dimensions.
value_sets declare named non-empty lists. Use either a plain array or an object when you want to hide the concrete values in policy-denial messages:
value_sets:
airlock_repos:
- airlock-dev/airlock
safe_fix_branches:
values:
- 'fix/*'
- 'feat/*'
expose_values: truearg_dimensions map a reusable name onto concrete tool argument paths. match controls how the value set is converted into runtime argument policy: in creates exact allow-list checks, glob_in creates glob-pattern checks, and each_in requires every value in an array argument to be allowed. normalize can be phone, email, lower, or trim.
arg_dimensions:
github_repo:
match: in
bindings:
github/push_files: repo
github/create_pull_request: repo
github_branch:
match: glob_in
bindings:
github/push_files: branch
github/create_pull_request: headProfiles and agents attach these dimensions through arg_scope:
profiles:
airlock_autofix:
arg_scope:
github_repo: airlock_repos
github_branch: safe_fix_branchesAt config load, Airlock expands arg_scope into concrete arg_policy constraints. Unknown dimensions, unknown value sets, empty dimensions, and declared argument controls that resolve to no effective runtime constraints are reported by airlock config check.
sandbox_presets
Reusable sandbox envelopes. See Sandboxing.
sandbox_presets:
local_transform:
filesystem:
allow_read: ['.']
allow_write: ['/tmp']
deny_read: ['~/.ssh', '~/.aws', '.env']
deny_write: ['.']
network:
allowed_domains: []
denied_domains: []clis
CLI tools exposed as named MCP tools. See CLI Discovery.
clis:
git:
discovered: ./git-commands.yaml
shell: /bin/bash
max_output_bytes: 30000
commands:
status:
exec: git status
params: {}
log:
exec: 'git log --oneline -n {count}'
params:
count:
type: number
required: false
default: 10apis
REST APIs exposed as MCP tools. See API Discovery.
apis:
petstore:
spec: ./petstore.json
base_url: https://petstore.example.com/v1
auth:
type: bearer # or "basic"
token: ${TOKEN}
timeout_ms: 30000
max_response_bytes: 1048576agents
Per-agent policy configuration.
agents:
claude-code:
extends: [readonly, developer] # Inherit from profiles
inherit_default: true # Inherit the top-level default_profile (default: true)
allow:
- github/*
ask:
- github/create_pr
remember_allow:
- tool: github/create_pr
expires_at: '2026-05-26T11:00:00.000Z'
- tool: github/list_prs
deny:
- exec/run
exec: # Shell command sub-policy
allow: ['git status', 'npm test*']
ask: ['git push*']
deny: ['sudo *', 'rm -rf *']
env:
PATH: '/usr/local/bin:/usr/bin:/bin'
http: # HTTP domain restrictions
domain_allowlist: ['api.github.com', '*.sentry.io']
arg_policy: # Per-tool argument constraints
google_workspace/manage_event:
calendar_id:
equals: work-calendar-id@group.calendar.google.com
label: Work
action:
allow: [create, update, delete]
github/push_files:
branch:
glob_in: safe_fix_branches
label: Safe branch
arg_scope: # Reusable argument constraints via arg_dimensions
github_repo: airlock_repos
middleware: # Optional per-agent configurable middleware
- name: rate-limiter
max_requests: 100
window_ms: 60000
per: agent
- name: output-size-limiter
max_lines: 200
max_chars: 30000
sandbox: # Agent-level sandbox
enabled: true
presets: [local_transform]
tool_overrides: # Tool variants
python/sandboxed:
alias_of: exec/run
description: 'Sandboxed Python'
sandbox_presets: [local_transform]
gcal_work_write:
alias_of: google_workspace/manage_event
description: 'Manage events on the Work calendar only. calendar_id must be work-calendar-id@group.calendar.google.com.'
args:
calendar_id:
equals: work-calendar-id@group.calendar.google.com
label: Workapprovals
Global approval provider config. See HITL Providers.
approvals:
provider:
type: telegram
bot_token: '${TELEGRAM_BOT_TOKEN}'
chat_id: '${TELEGRAM_CHAT_ID}'
timeout_ms: 300000
batch_window_ms: 10000provider may also be a list when you want multiple approval surfaces. For example, a self-hosted VPS can keep the dashboard enabled while also sending native APNs pushes to registered iOS devices:
approvals:
provider:
- type: dashboard
host: 0.0.0.0
port: 4112
- type: ios
team_id: ${APPLE_TEAM_ID}
key_id: ${APNS_KEY_ID}
key_path: /config/AuthKey_XXXXXXXXXX.p8
bundle_id: com.airlock.companion.ios
production: true
interruption_level: time-sensitiveAgent middleware is an optional array. If omitted, Airlock enables the default configurable middleware: schema-validator, untrusted-envelope, and output-injection-detector. Set middleware: [] for the bare fixed pipeline, or include { name: <middleware>, enabled: false } to disable one default. See Middleware Pipeline.
lint
Static hygiene rule configuration for airlock lint.
lint:
disable:
- dead-deny
severity:
unused-profile: warn
missing-env-ref: errorUse disable to permanently accept a rule for a config, and severity to re-grade a rule as info, warn, or error. CLI flags such as --disable and --rule override this block for one invocation.
security
Host blocking for built-in HTTP and OpenAPI tools. Airlock resolves domain names before outbound requests and fails closed if any resolved address is blocked or DNS cannot be verified.
Known limitation: this is a static DNS check. Full DNS-rebinding protection needs a pinned-resolution HTTP transport that connects to the verified IP while preserving the original hostname for Host, SNI, and certificate validation.
security:
blocked_hosts:
- '127.0.0.1'
- '::1'
- 'localhost'
- '10.*'
- '192.168.*'
- '172.16.*'
- '169.254.*'
allowed_local:
- 'host.docker.internal'audit
Audit log settings.
audit:
redact_fields:
- password
- token
- secret
- authorization
- api_keyserver
Gateway server settings (used in non-stdio mode).
server:
port: 4111
host: 127.0.0.1
api_secret: '${AIRLOCK_API_SECRET}'
auth_required: true
require_agent_tokens: true
expose_tools_api: true
allowed_origins:
- https://airlock.example.com
management_api:
enabled: true
api_secret: '${MANAGEMENT_API_SECRET}'
host: 127.0.0.1
port: 4113
insecure_remote_bind: false
expose_hook_api: trueFor a self-hosted or remote deployment, keep Airlock behind a TLS/authenticated edge or a private network, and keep the Airlock listener private when possible. If server.host is not loopback, Airlock requires auth_required: true and per-agent tokens at config load time. When Airlock is bound to loopback behind a reverse proxy, set require_agent_tokens: true to enforce the same profile isolation.
The server listener is the agent data-plane. It serves MCP transports (/agents/:agentId/mcp, /agents/:agentId/sse, /agents/:agentId/messages) and, when expose_tools_api enables it, the REST agent tool API (/agents/:agentId/tools and /agents/:agentId/tools/invoke).
api_secretis the data-plane fallback credential for tools API and MCP routes when an agent has no token. If an agent hastoken,/agents/:agentId/tools*and MCP routes require that agent token instead.auth_requiredrejects unauthenticated requests even when no secret/token is configured.require_agent_tokensrequires every configured agent to have its own token. This prevents the globalapi_secretfrom becoming a fallback MCP credential for tokenless profiles.allowed_originsis an exact allowlist for browserOriginheaders. Requests withoutOriginare allowed so non-browser MCP clients keep working.expose_tools_apicontrols the REST agent tool API on the data-plane listener (an agent-facing execution API using the same per-agent auth model as MCP routes). It is a mode:allexposes it for every agent,noneis a hard kill-switch, andper-agent(the default) exposes it only for agents that set their ownexpose_tools_api: true. Legacy booleans are accepted (true→all,false→none). Migration note: the default changed from on-for-all toper-agent(off unless an agent opts in) — setall, or opt the relevant agents in, to keep the previous behavior.management_api.enabledstarts a separate control-plane listener for/health,/hitl/*,/audit, the dashboard approval bridge (/events,/approve,/deny,/version*),/activity,/mobile/*including/mobile/approvals/stream,/admin/tools, and/hook. It is disabled by default. When enabled, every agent must settokenso the data-plane fallback cannot become an implicit agent credential.management_api.api_secretprotects the control-plane listener. If unset, Airlock temporarily falls back toserver.api_secretfor backward compatibility and emits a deprecation warning telling operators to setserver.management_api.api_secretseparately. If both secrets resolve to the same value, config validation warns so operators can rotate one.management_api.hostdefaults to127.0.0.1. Binding it beyond loopback requiresmanagement_api.insecure_remote_bind: true; without that explicit opt-in, config validation refuses to start.management_api.portdefaults to4113and must not equalserver.port. The agent data-plane and control-plane cannot share a socket.management_api.expose_hook_apicontrols/hookon the management listener.
expose_management_api and expose_hook_api are deprecated aliases for one release. They still map into management_api, but new configs should use the explicit block above.
To split an existing single-secret deployment, generate a fresh MANAGEMENT_API_SECRET, set server.management_api.api_secret: '${MANAGEMENT_API_SECRET}', recreate or restart Airlock, and update companion apps or dashboards to send Authorization: Bearer $MANAGEMENT_API_SECRET. Keep server.api_secret only as the data-plane fallback, or remove it when every agent has its own token and no fallback credential is needed. Per-device companion tokens are a future hardening step so one device can be revoked without rotating the shared management secret.
For a public MCP-only listener, disable the non-MCP APIs and use per-agent tokens:
server:
port: 4111
host: 127.0.0.1
auth_required: true
require_agent_tokens: true
expose_tools_api: false
allowed_origins:
- https://airlock.example.com
management_api:
enabled: false
agents:
claude-code:
token: '${CLAUDE_CODE_AIRLOCK_TOKEN}'
allow:
- github/list*Environment variable substitution
Any value of the form ${VAR_NAME} is replaced with the corresponding environment variable at config load time. This works for all string values in the config.
Example configs
examples/gateway.yaml— fully annotated reference configexamples/profiles.yaml— composable profile examplesexamples/sandbox-presets.yaml— sandbox preset and tool variant examplesexamples/local-dev.yaml— minimal local development config