Opinionated lightweight sandbox for daily dev tasks with coding agents. Built on Anthropic's sandbox-runtime (srt).
This project is named after the Blackwall in Cyberpunk 2077, a firewall NetWatch built to keep rogue AIs out of the Net.
Why Blackwall
Blackwall is an out-of-the-box sandbox for long-running agent tasks, mainly claude and codex. You get nearly the same experience as your daily dev work outside a sandbox, but much safer. At least the blast radius stays under control. Unlike the built-in sandboxes, it runs the whole agent inside srt, hooks and MCP servers included.
Comparison: blackwall vs claude/codex built-in sandboxes vs srt
Claude Code's Built-in Sandbox
| Blackwall | Claude Code's built-in sandbox | |
|---|---|---|
| Covers | The whole agent and every process it starts, MCP servers included | Shell commands only. Read, Edit, Write, and WebFetch run outside it and follow permission rules. Hooks, MCP servers, and apiKeyHelper run with your full access |
| Reads | Denied everywhere, except the folders it writes, system paths, and what presets need | Most of your machine by default, ~/.ssh and ~/.aws/credentials included |
| Hosts | Only what presets and claude need, or every public host with --allow-internet, whatever the permission mode |
Your allowed domains, but in bypassPermissions mode a host you never allowed connects without a prompt, unless you set strictAllowlist or allowManagedDomainsOnly |
| Localhost and LAN | Denied by the private-network preset, and local-network keeps localhost denied under "*" |
Loopback denied, but an allowed name that resolves into your LAN, like 10.0.0.0/8, connects |
| Way out | No built-in escape hatch; known limitations still apply | Claude can retry a blocked command unsandboxed, unless you set allowUnsandboxedCommands to false. excludedCommands run unsandboxed either way |
Codex's Built-in Sandbox
| Blackwall | Codex's built-in sandbox | |
|---|---|---|
| Covers | The whole agent and every process it starts, MCP servers included | MCP servers, web search, and connectors run outside it |
| Reads | Denied everywhere, except the folders it writes, system paths, and what presets need | Everything, unless you write a permission profile |
| Hosts | Only what presets and codex need, or every public host with --allow-internet |
Off by default. Once on, every host is reachable, unless you also enable features.network_proxy and list your domains |
.git |
Writable, except hooks and config |
Read-only, so git commit needs an approval |
srt Directly
Blackwall is srt underneath, with the setup done for you:
| Blackwall | srt directly | |
|---|---|---|
| Reads | Denied everywhere, except the folders it writes, system paths, and what presets need | Everything by default, ~/.ssh included |
| Hosts | Only what presets and claude or codex need |
None, until you list each one in ~/.srt-settings.json |
| Every public host | --allow-internet for one run, or "*" in network.allowedHosts |
"*" is rejected |
| Localhost and LAN | Denied by the private-network preset, and local-network keeps localhost denied under "*" |
Loopback denied, but an allowed name can still resolve into your LAN |
~/.claude |
~/.claude-blackwall, with your CLAUDE.md, rules, and skills copied in on every launch |
Must be writable for claude to run, so the agent can edit your hooks and settings |
| Agent files in the repo | Also .claude/settings.json, .claude/settings.local.json, and .claude/hooks, in --add-dir folders too |
.mcp.json, .claude/commands, and .claude/agents denied, beneath the current folder only |
| Tool setup | Presets for python, node, github, git, and pre-commit |
Yours to write: each tool's hosts, read paths, and cache folders |
| Project config | .blackwall/config.json, loaded only after --trust |
None, only --settings with a path |
| Inspection | --print-config, --print-file-access, --print-network-access, and --print-env |
--debug logs |
srt's security limitations apply to blackwall too.
What You Give Up
- Hosts and paths are fixed at launch. A new host means a config edit or
--allow-internetand a relaunch, not a prompt mid-session - No credential masking yet, while Claude Code's built-in sandbox can mask secrets
Installation
Needs Node.js 22.21 or later.
npm install -g blackwall-sandbox
Keep blackwall and srt outside every writable folder, including the current folder and any --add-dir folder. If a sandboxed command can rewrite either package, the next launch runs that code outside the sandbox.
Shell completion
To enable zsh completion with oh-my-zsh:
mkdir -p $ZSH_CUSTOM/completions
blackwall --completion zsh > $ZSH_CUSTOM/completions/_blackwall # run it again after upgrading blackwall
Without oh-my-zsh, save it to ~/.zfunc/_blackwall instead, and add fpath=(~/.zfunc $fpath) to ~/.zshrc, before compinit.
Usage
blackwall claude # run Claude Code in the sandbox
blackwall --add-dir ../another-repo claude # also let it write to another folder
blackwall --allow-internet claude # also let it reach every public host, but not localhost or your LAN
blackwall npm test # any command works, not only claude
blackwall --print-config claude # show the merged config, after your config and the launch-time grants
blackwall --print-file-access claude # show what it can read and write, as srt sees it
blackwall --print-network-access claude # show which hosts it can reach, as srt sees it
blackwall --print-env claude # show the env vars blackwall adds, and why
blackwall --trust # trust .blackwall/config.json in the current folder
blackwall --version # show the blackwall version
blackwall --completion zsh # print the zsh completion script
blackwall --help # show the usage
By default, a sandboxed command:
- Writes only to the current folder,
--add-dirfolders,/tmp/claude, and~/.cache-blackwall - Never writes
.git/configor.git/hooks, in the current or--add-dirfolders.git remote add,git config, andgit branch -mfail;git push -u,git branch -D, andgit switch -close their tracking update. Set remotes and upstreams on the host, or push withgit push origin HEAD - Never writes
.claude/settings.json,.claude/settings.local.json,.claude/hooks,.mcp.json,.claude/commands, or.claude/agentsin the current or--add-dirfolders. The names come from theagent-filespreset. A sandboxedclaudecan't save project permission rules either - Reads only those folders, plus the
allowReadpaths in the default config and presets - Connects only to the hosts in presets, and the ones Claude Code or Codex needs
Claude Code
blackwall claude uses ~/.claude-blackwall as its config folder instead of ~/.claude. For security, a sandboxed run never writes to ~/.claude, and reads only ~/.claude/plugins.
- Every launch copies your
CLAUDE.md,rules,skills,agents,commands, andoutput-stylesfrom~/.claude, replacing what was there, so edits to those copies don't last - Your plugins load from
~/.claude/pluginsread-only, without auto-update. Sandboxed sessions follow the plugin versions installed outside - Sessions, settings, and login stay separate
settings.jsonstarts with only yourenabledPlugins, since your other settings, like hooks and permissions, assume no sandbox. After that it's yours to edit
Log in with /login, or put CLAUDE_CODE_OAUTH_TOKEN in ~/.blackwall/.env.
Codex
blackwall codex uses ~/.codex-blackwall as its config folder instead of ~/.codex. A sandboxed run never writes to ~/.codex, and reads only ~/.codex/packages, where the standalone installer puts the codex binary.
- Every launch copies your
AGENTS.mdandagentsfrom~/.codex, plusskillsfrom~/.agents. These copies replace what was there, so edits to them don't last - Sessions,
config.toml,rules, and login stay separate. Put sandbox-specific rules in~/.codex-blackwall/rules/ - Codex's own sandbox is off (
-c sandbox_mode="danger-full-access"), since it can't start inside blackwall's. blackwall is the sandbox
Log in with blackwall codex login --device-auth.
Agent Skill
skills/blackwall teaches Claude Code and Codex to run, configure, and debug blackwall. Inside the sandbox, the agent can't run blackwall or edit its config, so the skill tells it how to read a denial and what to hand you.
blackwall claude and blackwall codex copy it into their config folders on every launch, so it always matches your blackwall. For agents you run outside the sandbox:
npx skills add vinta/blackwall -g
Configuration
Environment Variables
Put your env vars in ~/.blackwall/.env if you need them.
GH_TOKEN=xxx # generate a read-only PAT from https://github.com/settings/personal-access-tokens
CLAUDE_CODE_OAUTH_TOKEN=xxx # run `claude setup-token` to get one
Sandboxed commands get BLACKWALL_SANDBOX=1, so a script can tell it runs inside blackwall.
Default
src/config/default-config.json holds the default settings. Only keys in default-config.json can be set in your ~/.blackwall/config.json or .blackwall/config.json.
presets: ["agent-files", "python", "node", "github", "git", "pre-commit", "local-network", "private-network"]: The presets on by defaultnetwork.allowedHosts: []: No hosts beyond presets. An entry is a name, likeexample.comor*.example.com, or an IP literal, and can take a port, e.g.127.0.0.1:8000. srt refuses loopback, unspecified, link-local, multicast, broadcast, cloud metadata, and your machine's own addresses for every name. blackwall applies the same to IP literals under"*". These aren't in--print-confignetwork.deniedHosts: []: Hosts to refuse. A name is refused under"*"; without"*", an unlisted host is refused anyway. An IP literal or a range, like10.0.0.0/8, is refused as a literal, and also when an allowed name resolves into it. AnallowedHostsentry wins over adeniedHostsname. A range is overridden only by that IP itself inallowedHostsfilesystem.denyRead: ["/"]: Blocks every read, thenallowReadopens what dev tools needfilesystem.allowReadopens only~/.local/binunder~/.local, and presets add their tools' folders. Version managers like mise keep tools elsewhere in it, so add their folders to your user configsrt.enableWeakerNetworkIsolation: true: On macOS, lets Go tools likeghverify TLS certificates throughcom.apple.trustd.agent, which also opens a data exfiltration path through that service. On by default because thegithubpreset issrt.enableWeakerNestedSandbox: false: On Linux, lets the sandbox run inside Docker without privileged namespaces, which considerably weakens isolationsrt.allowPty: true: Lets sandboxed commands open pseudo-terminals, which TUIs need
srt holds srt settings, passed to srt untouched, each replacing its default. Blackwall doesn't read ~/.srt-settings.json, only the srt CLI does.
Presets
A preset is what one tool needs to work in the sandbox: hosts, read paths, env vars like its cache folder, shell lines, macOS services, and a why, which blackwall --print-env prints above the preset's env vars and shell lines. Each is a module in src/presets:
- Always on
- On by default
- private-network: Denies LAN hosts: RFC 1918, CGNAT, and IPv6 ULA ranges. To reach one, add its IP literal with the port to
network.allowedHosts, e.g.192.168.1.10:8080 - local-network: Denies
localhostand*.localhost. To reach a local server, add it with the port tonetwork.allowedHosts, e.g.localhost:8000 - git: Your git config and global ignore files. Commit signing off, since gpg can't reach
~/.gnupg. Keeps srt's proxy login out of your credential helpers. Denies writes to.git/hooksand.git/configin--add-dirfolders. Drop it only if you won't run git: with a~/.gitconfigit can't read, git refuses to start - github: GitHub, for
git,gh, and raw files, and gh's own config folder - python: PyPI, for
pipanduv, and uv's config, Python installs, and tools - node: The npm registry, and Node.js installed by fnm
- pre-commit: Its cache folder, and hook repos clone through srt's proxy
- agent-files: Denies writes to
.claude/settings.json,.claude/settings.local.json,.claude/hooks,.mcp.json,.claude/commands, and.claude/agentsin the current and--add-dirfolders: an unsandboxedclauderuns what they hold, even before you trust the folder. Drop it and a sandboxed command can plant commands that run on the host
- private-network: Denies LAN hosts: RFC 1918, CGNAT, and IPv6 ULA ranges. To reach one, add its IP literal with the port to
- Off by default
- clipboard: On macOS, lets
clauderead an image you copied, e.g. a Cmd+Ctrl+Shift+4 screenshot, when you paste it. Every sandboxed command can then read what you copy, passwords included, and replace what you paste next - screenshots: Reads your screenshot folder, so a screenshot you copy in Finder and paste as its path reaches
claude. That's~/Desktop, unless you moved it withdefaults write com.apple.screencapture location ~/Screenshots, which also keeps the rest of your Desktop unreadable
- clipboard: On macOS, lets
tmpdir, mac, and linux aren't in the presets list, so overriding it can't drop them.
Preset paths follow XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOME, and XDG_CACHE_HOME when you set them.
User Config
~/.blackwall/config.json: add to the defaults, or replace them with overrideDefaults
Put your customizations in ~/.blackwall/config.json:
{
"filesystem": { "allowRead"
