Sandbox Presets and Variants
Sandboxing lets you turn one underlying capability into multiple policy shapes.
That is useful when you want to reduce approval fatigue without handing out the full-power version of a tool.
The core pattern
Use alias_of to create tool variants:
- a tightly sandboxed variant in
allow - a broader variant in
ask
For example:
python/sandboxed→ local transforms only, auto-approvedpython/full→ broader filesystem/network, requires approvalpython/github→ GitHub-only network access, requires approval
OS-level sandboxing
The built-in python/eval tool uses macOS sandbox-exec to run Python in a kernel-enforced sandbox:
- All filesystem writes are denied at the OS level
- All network access is denied at the OS level
- Process execution is allowed (to run Python itself)
- File reads are allowed (for Python stdlib and your project files)
This is real OS-level isolation, not just config-level restrictions. The kernel enforces the sandbox profile regardless of what the Python code tries to do.
providers:
python: builtin
agents:
claude-code:
allow:
- python/eval # Safe — OS-level sandbox denies writes and networkReusable presets
Top-level sandbox_presets let you define reusable envelopes once and apply them:
sandbox_presets:
local_transform:
filesystem:
allow_read: ['.']
allow_write: ['/tmp', '/private/tmp']
deny_read: ['~/.ssh', '~/.aws', '.env']
deny_write: ['.']
network:
allowed_domains: []
github_only:
network:
allowed_domains:
- 'github.com'
- '*.github.com'
- 'api.github.com'
denied_domains: []
npm_registry:
network:
allowed_domains:
- 'registry.npmjs.org'
- 'npmjs.org'Apply presets agent-wide or per tool variant:
sandbox.presetsapplies to the whole agent sandbox baselinetool_overrides.<tool>.sandbox_presetsapplies only to that tool variant
Full example
agents:
claude-code:
allow:
- python/sandboxed
ask:
- python/full
- python/github
sandbox:
enabled: true
presets:
- local_transform # Agent-wide baseline
tool_overrides:
python/sandboxed:
alias_of: exec/run
description: 'Run Python for local transformations only'
python/full:
alias_of: exec/run
description: 'Run Python with broader permissions after approval'
sandbox:
filesystem:
allow_write: ['.', '/tmp', '/private/tmp']
deny_write: []
network:
allowed_domains: ['pypi.org', '*.pythonhosted.org']
python/github:
alias_of: exec/run
description: 'Run Python with GitHub-only network access'
sandbox_presets:
- github_only # Reuses the named presetPreset merge rules
- Explicit
sandboxvalues on a tool override win over preset values when they conflict - Deny lists are additive — denies from presets and explicit config are combined
- Allow lists usually replace the previous value so the tool variant can intentionally define a tighter or broader envelope
- Agent-level presets provide the baseline; tool-level presets and explicit sandbox config refine it
Approval visibility
When a sandboxed tool call requires approval, Airlock includes the resolved sandbox summary in the approval notification:
- Which presets were applied
- Whether network is disabled or limited to specific domains
- Where writes are allowed
- Which paths are explicitly denied
Audit entries also include the resolved sandbox context alongside the tool arguments.
What is validated
Current macOS smoke test coverage verifies:
- Allowed writes succeed
- Disallowed writes fail
- Denied reads fail
allow_readcarve-outs work- Deny-all network blocks outbound access
- Allowlisted network domains work for the tested runtime path
Practical guidance
- Use a sandboxed
allowvariant for cheap local work: JSON transforms, parsing, codegen, text munging - Keep networked or repo-writing variants in
askuntil you've verified the runtime - Prefer a small number of named presets:
local_transform,github_only,npm_registry,readonly_repo - If a tool needs a one-off tweak, put it in the tool override instead of creating a new preset