Sandcat
A Docker & dev container setup for securely running AI agents in `--dangerous` mode. All container traffic is routed through a transparent mitmproxy, enforcing network access rules and injecting secrets.
Install / Use
/learn @VirtusLab/SandcatQuality Score
Category
Development & EngineeringSupported Platforms
README
Sandcat
Sandcat is a Docker & dev container setup for securely running AI agents. The environment is sandboxed, with controlled network access and transparent secret substitution. All of this is done while retaining the convenience of working in an IDE like VS Code.
All container traffic is routed through a transparent mitmproxy via WireGuard, capturing HTTP/S, DNS, and all other TCP/UDP traffic without per-tool proxy configuration. A straightforward allow/deny list-based engine controls which network requests go through, and a secret substitution system injects credentials at the proxy level so the container never sees real values.
This repository contains:
- a bash CLI to initialize the sandbox for a project, copying and customizing
the necessary files (see
cli/) - reusable proxy definitions under
cli/templates/devcontainer/sandcat/:Dockerfile.wg-client,compose-proxy.yml, andscripts/that perform the network filtering & secret substitution - template application and dev container configuration under
cli/templates/devcontainer/:Dockerfile.app,compose-all.yml,devcontainer.json. This should be fine-tuned for each project and specific development stack, to install required tools and dependencies.
Sandcat can be used as a devcontainer setup, or standalone, providing a shell for secure development.
Quick start
1. Install sandcat CLI
The CLI is a helper script and thin wrapper around docker-compose that simplifies the process of initializing and starting the sandbox.
It has two main tasks:
- copy the necessary configuration files from the
cli/templatesdirectory into your project and customize them based on your choices (development stack, etc.) - run
docker composecommands with the correct compose file automatically detected, so you don't have to remember the file names or paths.
The CLI can itself be run through a docker image that we publish to our repository, so that no local installation is required; or installed locally by cloning this git repository.
Run as docker image (recommended)
# Pull the image to local docker
docker pull ghcr.io/virtuslab/sandcat
# Add to your .bashrc or .zshrc
alias sandcat='docker run --rm -it -v "/var/run/docker.sock:/var/run/docker.sock" -v"$PWD:$PWD" -v"$HOME/.config/sandcat:$HOME/.config/sandcat" -w"$PWD" -e TERM -e HOME ghcr.io/virtuslab/sandcat'
The CLI needs access to your current directory (to copy project configuration),
the host Docker socket (to manage sandbox containers), your user config
directory (~/.config/sandcat/ to initialize the settings file), and a couple
of environment variables (TERM for terminal handling, HOME so Docker Compose
can resolve ~ in volume mounts).
Using the Docker image disables the editor integration (vi installed in the
image will be used instead of your host editor). Host environment variables are
not forwarded unless you add -e flags explicitly.
The image runs as root, to avoid permission issues with the host Docker socket.
On Colima file ownership is mapped automatically, on Linux you should add
--user parameter accordingly.
Local install
# Clone the repo
git clone https://github.com/VirtusLab/sandcat.git
# Add the sandcat bin directory to your path (add this to your .bashrc or .zshrc)
export PATH="$PWD/sandcat/cli/bin:$PATH"
yq is required to edit compose files.
2. Initialize the sandbox for your project
sandcat init
This prompts you to select the agent type, IDE (for devcontainer mode), and development stacks to install. You can also pass flags to skip prompts:
sandcat init --agent claude --ide vscode --stacks "python,node"
# With optional features (proxy TUI, 1Password integration)
sandcat init --features "1password" --agent claude --ide vscode
Available stacks: node, python, java, rust, go, scala, ruby,
dotnet. Versions default to LTS where available (e.g. Node.js LTS, Java LTS).
To change versions after init, edit the mise use lines in the generated
.devcontainer/Dockerfile.app.
Selecting scala automatically includes java as a dependency. Stacks also
install the corresponding VS Code extension (e.g. rust-analyzer for Rust,
metals for Scala).
Optional volume mounts (Claude config, .git, .idea) are configurable in the
generated compose file. Claude config mounts are active by default for the
Claude agent; .git and .idea are commented out. Set SANDCAT_* environment
variables for scripted usage. See the CLI README for the full
list of flags and environment variables.
3. Start the sandbox
CLI mode:
# Open a shell in the agent container
sandcat run
# Rebuild images first (after editing Dockerfile.app or scripts)
sandcat run --build
# Start your agent cli (e.g. claude). Because you're in a sandbox, you can use yolo mode!
# (an alias for --dangerously-skip-permissions)
claude-yolo
Customizing the generated files
compose-all.yml — network_mode: "service:wg-client" routes all traffic
through the WireGuard tunnel. The mitmproxy-config volume gives your container
access to the CA cert, env vars, and secret placeholders. The ~/.claude/*
bind-mounts forward host Claude Code customizations — remove any mount whose
source does not exist on your host.
Dockerfile.app — uses mise to manage language
toolchains. Stacks selected during sandcat init are added as RUN mise use -g
lines. You can edit the versions or add more stacks after init. Some runtimes
need extra configuration to trust the mitmproxy CA — see TLS and CA
certificates.
devcontainer.json — includes VS Code hardening settings (credential socket
cleanup, workspace trust, disabled local terminal). See Hardening the VS Code
setup for details.
Settings format
Settings are loaded from up to three files (highest to lowest precedence):
| File | Scope | Git |
|------|-------|-----|
| .sandcat/settings.local.json | Per-project overrides | Ignored (add to .gitignore) |
| .sandcat/settings.json | Per-project defaults | Committed |
| ~/.config/sandcat/settings.json | User-wide defaults | N/A |
All three files use the same JSON format. Missing files are silently skipped. If no files exist, the addon disables itself.
Merge rules:
env— merged; higher-precedence values overwrite lower ones.secrets— merged; higher-precedence entries overwrite lower ones.network— concatenated; highest-precedence rules come first. Since rules are evaluated top-to-bottom with first-match-wins, this means local rules take priority over project rules, which take priority over user rules.
A typical setup keeps user-specific settings (git identity, API keys) in the user file, project-wide network rules in the project file, and developer overrides in the local file:
~/.config/sandcat/settings.json (user — created by sandcat init on first
run):
{
"env": {
"GIT_USER_NAME": "Your Name",
"GIT_USER_EMAIL": "you@example.com"
},
"secrets": {
"ANTHROPIC_API_KEY": {
"value": "sk-ant-real-key-here",
"hosts": ["api.anthropic.com"]
},
"GITHUB_TOKEN": {
"value": "ghp_your-token-here",
"hosts": ["github.com", "*.github.com", "*.githubusercontent.com"]
}
},
"network": [
{"action": "allow", "host": "*.github.com"},
{"action": "allow", "host": "github.com"},
{"action": "allow", "host": "*.githubusercontent.com"},
{"action": "allow", "host": "*.anthropic.com"},
{"action": "allow", "host": "*.claude.ai"},
{"action": "allow", "host": "*.claude.com"}
]
}
.sandcat/settings.json (project, committed):
{
"network": [
{"action": "allow", "host": "*", "method": "GET"}
]
}
.sandcat/settings.local.json (project, git-ignored):
{
"network": [
{"action": "allow", "host": "internal.corp.dev"}
]
}
With these files, the merged network rules are (local first, then project, then
user): allow internal.corp.dev, then the project wildcard GET rule, then the
user's GitHub/Anthropic rules. Env and secrets come from the user file since
neither project file defines them.
Warning: the default project settings allow all GET traffic, which means the agent can read arbitrary web content — a vector for prompt injection. Stricter settings would narrow this to known service domains. Note that the user-level settings allow full access to GitHub, which can be used to read untrusted content (prompt injection) or push data out (exfiltration). Malicious code might also be generated as part of the project itself.
Applying configuration changes
Mitmproxy reads settings files only at startup (no hot-reload), and the app
container sources sandcat.env only during its entrypoint. After editing any
settings file, you need to restart services for changes to take effect.
You can use the CLI helper commands:
sandcat edit project-settings # project network rules (.sandcat/settings.json)
sandcat edit user-settings # API keys, git identity (~/.config/sandcat/settings.json)
sandcat edit dockerfile # container Dockerfile (.devcontainer/Dockerfile.app)
sandcat edit compose # Docker Compose file (.devcontainer/compose-all.yml)
After editing a settings file, restart the proxy to apply changes:
sandcat restart-proxy
Note that VS Code's Rebuild Container only rebuilds the agent service — it
does not restart mitmproxy or wg-client. Use sandcat restart-proxy to
apply settings changes.
Network access rules
The network array defines ordered access rules evaluated top-to-bottom. First
matching rule wins (like iptables). If no rule matches, the request is
denied.
Each rule has:
action—"allow"or"deny"(required)
