A single command to start a coding agent (Claude Code or any other) inside an
isolated, egress-filtered cage on macOS — composed from
agent-safehouse (filesystem + process isolation)
and mitmproxy (TLS-terminating egress allowlist),
configured by one .agent-creance.yaml file.
Status: early development (pre-v0.1), under active development. The v0.1 core is implemented:
setup,init, andrunwork end to end, along with the egress-policy commands (allow/deny/policy/import),doctor,status, andlogs. The full design lives indocs/design.md.
- Go 1.26+ (to build/install)
- macOS (v0.1 is macOS-only)
- For running a cage:
agent-safehouseandmitmproxyonPATH.setupandruncheck for these and tell you how to install any that are missing.
You need Go 1.26+ and, to actually run a cage, agent-safehouse and mitmproxy
on your PATH (macOS only — see Requirements). Install
agent-creance (see Install below), then:
agent-creance setup # once per machine: trust the mitmproxy CA, install
# the skill, and scaffold the global config
cd your-project
agent-creance init # once per project: write .agent-creance.yaml
agent-creance run # start the cage and your agent inside itsetup is a one-time, per-machine step; init is one-time per project. If you
skip them, run won't fail with a stack trace — it refuses early with a pointer
to whichever command you still need. For the full command reference, see
docs/design.md.
Both methods below install the agent-creance binary into go env GOPATH/bin
(usually ~/go/bin). Make sure that directory is on your PATH — if it isn't,
add it: export PATH="$PATH:$(go env GOPATH)/bin".
# Quickest — no clone:
go install github.com/tobyS/agent-creance/cmd/agent-creance@latest
# From source (stamps the build version into `agent-creance version`):
git clone https://github.com/tobyS/agent-creance.git
cd agent-creance
make installThe go install build reports its version as dev (cosmetic — it doesn't affect
how the cage runs); the make install build stamps the real version.
agent-creance setup scaffolds a global config at ~/.config/agent-creance.yaml
with the "always-allowed" baseline a caged Claude agent needs — the Claude Code
API/OAuth hosts plus Anthropic's public documentation hosts, so routine "how does
Claude Code X work" lookups don't get soft-denied. setup never modifies an
existing global config; it only writes the file when none is present.
If your global config predates the documentation hosts (added in AC-0048), add
them under network.egress.allow to read Anthropic's docs from inside the cage.
They are credential-free and scoped to GET (code.claude.com to /docs; the
two legacy hosts redirect to the already-allowed platform.claude.com):
network:
egress:
allow:
- host: code.claude.com
mode: intercept
paths: ["/docs/"]
methods: [GET]
- host: docs.anthropic.com
mode: intercept
methods: [GET]
- host: docs.claude.com
mode: intercept
methods: [GET]agent-creance init scaffolds a project .agent-creance.yaml. On an interactive
terminal it also offers to do the tedious allowlist/port wiring for you — each as
its own yes/no prompt, all auto-skipped when there is no TTY (so CI behaves as
before):
- Import allowed web domains from the project's Claude Code settings
(
WebFetch(domain:…)in.claude/settings.json/settings.local.json, plussandbox.network.allowedDomains) as GET-onlyinterceptrules. - Import MCP servers from
.claude/settings*.json,.mcp.json, and~/.claude.json: remote servers becomepassthroughallow rules; an MCP server bound tolocalhostbecomes ahost_servicesport. - Detect dev ports from
docker-compose.yml,package.jsonscripts,Procfile, and.env.
It then shows the resulting config and asks you to confirm before writing.
agent-creance setup does the same for the global baseline, seeding it from your
global ~/.claude config when it first creates ~/.config/agent-creance.yaml.
For everything that can't be inferred statically, init offers to print a prompt
you hand to your agent; the agent writes a config fragment (documentation hosts,
remaining ports) which you review and merge with:
agent-creance import agent-creance.suggested.yaml # add --yes for non-interactive useimport strict-validates the fragment, shows the merged result, and writes only
on confirmation.
agent-creance can inject a GitHub token into the cage's requests so the agent
authenticates without ever holding the token — you register a reference
(op://…, keychain://…, or env://…), and the proxy resolves it host-side and
overwrites the Authorization header at egress. Register one with:
agent-creance credential add github --source op://Private/GitHub/token --bearerUse a fine-grained PAT scoped to the one repo you want the agent to touch (Repository access → that repo only; Metadata: Read, Issues: Read and write, and Contents: Read). Then bind it to GitHub's REST API, scoped to your repo:
network:
egress:
allow:
- host: api.github.com
paths: ["/repos/OWNER/REPO"]
methods: [GET, POST, PATCH, PUT, DELETE]
inject: github
env:
# gh won't send a request when it thinks it's logged out; the proxy overwrites
# this placeholder. It is NOT a secret and NOT a boundary.
GH_TOKEN: "ghp_phantom_the_proxy_overwrites_this"
gh's porcelain (gh issue,gh pr,gh repo) runs over GraphQL, a single endpoint (POST api.github.com/graphql) whose target repo lives in the request body — which the egress filter cannot see. A repo-scoped token does not help here: it bounds writes and private reads, but every public repo is world-readable, so opening/graphqllets the agent read any public repo's issues, PRs, and files. That is an unbounded, attacker-controllable content channel — exactly the "read malicious content" vector the cage exists to close, and GitHub is where an attacker would plant it.The safe way is the scoped-REST config above: the agent works with issues via REST endpoints under
/repos/OWNER/REPO/…(call them withgh api /repos/OWNER/REPO/issues …orcurl, and tell your agent to prefer them inCLAUDE.md).gh's porcelain commands won't work, andgh auth statusreports a spurious "token invalid" (it pings an un-scopable root path) — but the agent cannot read repos you didn't allow. Only open/graphqlif you understand and accept that it removes that guarantee.
The full model — the two auth axes, overwrite/fail-closed semantics, the 472
refusal, and per-project scoping — is in
docs/design.md under "Credential injection".
agent-creance ships tab-completion scripts for bash, zsh, fish, and
PowerShell (generated by agent-creance completion <shell>). To try it in the
current session:
source <(agent-creance completion zsh) # or: bashTo enable it for every new session (macOS, Homebrew paths):
# zsh — needs `autoload -U compinit; compinit` in ~/.zshrc
agent-creance completion zsh > $(brew --prefix)/share/zsh/site-functions/_agent-creance
# bash — needs the bash-completion package
agent-creance completion bash > $(brew --prefix)/etc/bash_completion.d/agent-creanceStart a new shell for the change to take effect. Run
agent-creance completion <shell> --help for fish/PowerShell and Linux paths.
make help # list all tasks
make test # fast unit + CLI tests, race detector on
make lint # go vet + golangci-lint (run `make tools` once to install the linter)
make hooks # install the git pre-commit hook (gofmt + vet + tests)
make build # build ./bin/agent-creance with version metadata
make run ARGS="doctor"cmd/agent-creance/— tinymain, just calls intointernal/cli.internal/cli/— cobra command tree and theAppcomposition root.internal/buildinfo/— version metadata + tested-against tool versions.internal/prereq/— prerequisite detection and version-skew classification.internal/sysdep/— interfaces over the OS (the testability seam) and the real implementations;sysdeptest/holds the test fakes.
Logic never touches the OS directly — it goes through internal/sysdep
interfaces so tests inject fakes. Pure logic is covered by table-driven tests,
generated artifacts by golden files (-update to regenerate), and end-to-end
CLI behavior by testscript
.txtar scenarios. Anything that shells out to the real agent-safehouse /
mitmproxy is gated behind the integration build tag (make test-integration).
Apache-2.0.