Skip to main content

blackwall

Run, configure, and debug blackwall, the sandbox for claude, codex, and other dev commands (srt: Seatbelt on macOS, bubblewrap on Linux). Use when the user asks whether this session is sandboxed or what it can read, write, or reach, mentions blackwall, wants a command run sandboxed, or wants the sandbox to reach a path, host, or tool: ~/.blackwall/config.json, .blackwall/config.json, presets, --add-dir, --trust. Also use inside a sandboxed session when a command fails with `Operation not permitted` on a path that exists, `CONNECT tunnel failed, response 403`, ENOTFOUND or `nodename nor servname provided` for every host, a write under /tmp or ~ that fails or vanishes, or blackwall itself refuses with `can't run inside a sandbox`: those are blackwall denials, not project bugs. Not for configuring Claude Code's built-in sandbox or Codex's sandbox_mode.

Source facts

Repository
vinta/blackwall
Last source activity
October 11, 2026 at 11:53
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
blackwall
description
Run, configure, and debug blackwall, the sandbox for claude, codex, and other dev commands (srt: Seatbelt on macOS, bubblewrap on Linux). Use when the user asks whether this session is sandboxed or what it can read, write, or reach, mentions blackwall, wants a command run sandboxed, or wants the sandbox to reach a path, host, or tool: ~/.blackwall/config.json, .blackwall/config.json, presets, --add-dir, --trust. Also use inside a sandboxed session when a command fails with `Operation not permitted` on a path that exists, `CONNECT tunnel failed, response 403`, ENOTFOUND or `nodename nor servname provided` for every host, a write under /tmp or ~ that fails or vanishes, or blackwall itself refuses with `can't run inside a sandbox`: those are blackwall denials, not project bugs. Not for configuring Claude Code's built-in sandbox or Codex's sandbox_mode.
license
MIT
compatibility
Node.js 22.21+; macOS (Seatbelt) or Linux (bubblewrap)
allowed-tools
Bash(blackwall --print-config:*) Bash(blackwall --print-file-access:*) Bash(blackwall --print-env:*) Bash(blackwall --version)
# Blackwall `blackwall <command>` runs the command under [srt](https://github.com/anthropics/sandbox-runtime) with a policy fixed at launch: - Writes: the current folder, `--add-dir` folders, `/tmp/claude`, `~/.cache/blackwall`. Write-denied even inside those: `~/.blackwall`, `.blackwall` in the current folder, `.git/hooks` and `.git/config`, and srt's own list for the current folder (`.zshrc`, `.gitconfig`, `.mcp.json`, `.claude/commands`, …), which `blackwall --print-file-access` prints. - Reads: those folders, the system paths and preset paths in the default config, and `allowRead` entries from the user and project configs. The rest of `~` is invisible. - Network: only hosts from presets, the `claude` and `codex` adapters, and `network.allowedDomains`, all through srt's proxy. The sandbox has no direct DNS, so a tool that ignores `HTTPS_PROXY` can't resolve any host. A denial is the policy working. The fix is a grant plus a relaunch, never a retry, a copy into another folder, or `sudo`. ## Inside or outside? Inside a sandboxed session (`blackwall claude`, `blackwall codex`), `$BLACKWALL_SANDBOX` is `1`. blackwall 0.2.1 and older don't set it; there, one of these holds: - `$CLAUDE_CONFIG_DIR` is `~/.claude-blackwall`, or `$CODEX_HOME` is `~/.codex-blackwall` - `$GH_CONFIG_DIR`, `$UV_CACHE_DIR`, or `$NPM_CONFIG_CACHE` is under `~/.cache/blackwall` - `$TMPDIR` is exactly `/tmp/claude` (`/private/tmp/claude` on macOS) Inside, `blackwall` is unavailable: every flag, `--version` included, exits 2 with `blackwall: can't run inside a sandbox; run it from a plain terminal`. Diagnose from the error text and the grant list above, read the project's `.blackwall/config.json` if one exists, then hand the user the exact edit and commands. Outside (a plain terminal, or an agent that isn't sandboxed), run the `--print-*` flags and edit the user config yourself. ## Reading a denial | The command printed | Cause | Grant | |---|---|---| | `Operation not permitted` on a path that exists (macOS). On Linux the path reads as missing, or a write succeeds and the file is gone after the run | path outside the read or write grants | `filesystem.allowRead` or `filesystem.allowWrite` for one path; `--add-dir DIR` for a whole folder, read and write | | `curl: (56) CONNECT tunnel failed, response 403`; git: `unable to access '…': CONNECT tunnel failed, response 403`; Node's `fetch`: `fetch failed` caused by `Request was cancelled.`; Node's `http`: `ERR_PROXY_TUNNEL`, `HTTP/1.1 403 Forbidden` | host outside the allowlist | `network.allowedDomains`, or the preset for that tool | | `ENOTFOUND`, `getaddrinfo` failure, `[Errno 8] nodename nor servname provided, or not known`, for allowed hosts too | the tool ignores `HTTPS_PROXY` | make it use the proxy; no config change. curl, git, gh, npm, pip, uv, and urllib honor it already, and so do Node's `fetch` and `http` (22.21+) through the `node` preset's `NODE_USE_ENV_PROXY=1` | | `blackwall: …/.blackwall/config.json: not trusted. Review it, then run \`blackwall --trust\`` | project config is new or edited | the user reviews it and runs `blackwall --trust` | | `blackwall: …/.blackwall/.env: loads only with a trusted .blackwall/config.json` | project `.env` without a trusted config | add `.blackwall/config.json` (`{}` is enough), then `--trust` | | `blackwall: unknown preset <name>`; `blackwall: <file>: unknown key <key>`; `must be a list of strings` (exit 2) | config typo | fix the key; preset names are under Config | | `sandbox_apply: Operation not permitted` from `codex` | Seatbelt can't nest | `blackwall codex` already passes `-c sandbox_mode="danger-full-access"`; a codex started from inside another sandboxed command needs the same flag | | git: `could not write config file .git/config: Operation not permitted`. `git remote add`, `git config`, `git branch -m` fail; `git push -u`, `git branch -D`, `git switch -c` exit 0 but lose the tracking update | `.git/config` is write-denied, no grant lifts it | none. The user sets remotes and upstreams on the host; push with `git push origin HEAD` | Noise, harmless, the command still ran: - git: `fatal: failed to store: -60008`, the keychain credential helper can't save - `make: error: couldn't create cache file '/var/folders/…/xcrun_db-…' (errno=Operation not permitted)`, same from `xcrun` - pip: `WARNING: The directory '…/Library/Caches/pip' or its parent directory is not owned or is not writable … The cache has been disabled` ## Granting the narrowest thing 1. Confirm the denial: a marker from "Inside or outside?" plus a row above. Then ask whether the task needs the grant at all: writing under the current folder or `$TMPDIR`, or `npx` instead of a global install, needs none. 2. Pick the grant, narrowest first: - One run, one folder, read and write: `blackwall --add-dir DIR <command>`. Under `blackwall claude` the folder is also passed to claude as `--add-dir`. - One path: `filesystem.allowRead` or `filesystem.allowWrite`. One host: `network.allowedDomains` (`*.example.com` wildcards work). Everything one tool needs: its preset. - Per project: `.blackwall/config.json`. Per machine: `~/.blackwall/config.json`. Secrets: `~/.blackwall/.env` or `.blackwall/.env`, never a config file. - `"*"` in `allowedDomains` and `overrideDefaults.filesystem.denyRead` open the sandbox; propose them only when the user asks for an open sandbox. - Credentials under `~/.ssh`, `~/.aws`, `~/.gnupg`, and writes to `~/.claude`, `~/.codex`, `~/.blackwall` are what the sandbox exists to deny: report the need to the user instead of proposing a grant. 3. Deliver. Inside: print the JSON to add, the file it goes in, and the commands, for the user to run outside. Outside: write `~/.blackwall/config.json` yourself; for a project config, write it and leave `blackwall --trust` to the user, who re-runs it after every edit. `--trust` exists so a human reviews the file. 4. Relaunch: config loads at launch, so the user restarts the sandboxed command and reruns what failed. Done when it passes with that grant and nothing broader. ## Config `~/.blackwall/config.json` and `.blackwall/config.json` take the same keys. Lists from both are added to the defaults. `overrideDefaults` replaces a default value, the project's winning over the user's, and is applied first. `~` expands. ```json { "network": { "allowedDomains": ["api.example.com", "*.example.com"] }, "filesystem": { "allowRead": ["~/.npmrc", "/path/to/other/repo"], "allowWrite": [] }, "overrideDefaults": { "presets": ["github", "git"], "allowPty": false } } ``` - Presets: `python`, `node`, `github`, `git`, `pre-commit` are on by default; `tmpdir`, `mac`, `linux` are always on. Each is one tool's hosts, read paths, and env vars. What each grants: https://github.com/vinta/blackwall#presets - Dropping `git` from `presets` makes every sandboxed `git` refuse to start when `~/.gitconfig` exists. - `.blackwall/.env` keys override `~/.blackwall/.env` and reach only the sandboxed command. - Verify outside: `blackwall --print-config claude` (merged config with the launch-time grants), `blackwall --print-file-access claude` (srt's view), `blackwall --print-env claude` (env vars blackwall adds, each with its why). ## Run and install ```bash npm install -g blackwall-sandbox # Node.js 22.15+ sudo apt-get install -y bubblewrap socat ripgrep # Linux only sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 # Ubuntu 24.04+ only blackwall claude # or codex, or any command blackwall --add-dir ../other-repo claude blackwall --trust # in the project, after reviewing .blackwall/config.json ``` `blackwall` with no command prints the one-line usage. There is no `--help`: `blackwall --help` runs a command named `--help`. Exit 2 means a usage or config error. ## Under `blackwall claude` and `blackwall codex` - claude's config folder is `~/.claude-blackwall`, codex's is `~/.codex-blackwall`. `CLAUDE.md`, `rules`, `skills`, `agents`, `commands`, `output-styles` (claude) and `AGENTS.md`, `agents`, `rules`, plus `~/.agents/skills` (codex) are copied from the host folders on every launch, so edits to the copies vanish. User-level instruction and skill edits happen outside, in `~/.claude` or `~/.codex`; files in the current folder persist. - `~/.claude-blackwall/settings.json` is separate and starts with only `enabledPlugins`: host hooks and permissions don't apply. Plugins load from `~/.claude/plugins` read-only, auto-update off. - Login is separate: `/login`, or `CLAUDE_CODE_OAUTH_TOKEN` in `~/.blackwall/.env`; for codex, `blackwall codex login --device-auth`. - Commits are unsigned: `commit.gpgsign=false` is injected because gpg can't reach `~/.gnupg`. - Temp files go under `$TMPDIR` (`/tmp/claude`); the rest of `/tmp` is write-denied. - Codex's own sandbox is off inside; blackwall is the sandbox.
View on GitHub