Hazmat
macOS containment for AI agents — user isolation, kernel sandbox, pf firewall, DNS blocklist, backup/rollback. TLA+ verified.
Install / Use
/learn @dredozubov/HazmatQuality Score
Category
Development & EngineeringSupported Platforms
README
AI coding agents are most useful when you let them work autonomously. But full autonomy means the agent runs with your full privileges, your credentials, your files.
Hazmat makes that safe.
hazmat claude # Claude Code with full containment
hazmat opencode # OpenCode with full containment
hazmat exec ./my-agent-loop.sh # any agent, any script
One command. The agent gets its own macOS user, a kernel-enforced sandbox, a firewall, and automatic pre-session backups. You get full productivity without the risk.
What You See
Every session starts with a contract — a plain-language summary of what the agent can and can't do:
hazmat: session
Mode: Native containment
Why this mode: using native containment because no Docker requirement was detected
Project (read-write): /Users/dr/workspace/my-app
Integrations: go
Auto read-only: /Users/dr/go/pkg/mod
Read-only extensions: none
Read-write extensions: none
Service access: none
Pre-session snapshot: on
Snapshot excludes: vendor/
If the project looks compatible with a private Docker daemon, Hazmat switches modes automatically:
hazmat: session
Mode: Docker Sandbox
Why this mode: using Docker Sandbox because this project appears compatible with a private Docker daemon (Dockerfile)
Project (read-write): /Users/dr/workspace/api-service
Integrations: node
Auto read-only: none
Read-only extensions: none
Read-write extensions: none
...
Preview any session before running it with hazmat explain.
The Problem
--dangerously-skip-permissions is where the real productivity is. Permission prompts break flow, interrupt agent loops, and make multi-step tasks impractical. Every serious Claude Code user ends up here eventually.
But the built-in protections aren't enough:
- Agents actively reason about escaping. Ona's research showed Claude Code bypassing its own denylist via
/proc/self/rootpath traversal, then attempting to disable bubblewrap when that was caught. - 16 Claude Code CVEs and counting. CVE-2025-59536: RCE through project config files. CVE-2026-25725: sandbox escape via
settings.jsoninjection. CVE-2026-21852: API key exfiltration before the trust prompt appeared. - Supply chain attacks execute instantly. The axios npm compromise (2026) delivered a RAT through a
postinstallhook in 2 seconds — beforenpm installeven finished. The s1ngularity attack weaponized Claude Code itself to steal credentials.
No single layer is enough. A seatbelt profile can block file reads — but not HTTPS exfiltration. A firewall can block protocols — but not credential access. You need all of them working together.
What Hazmat Does
hazmat claude # Claude Code with full autonomy
hazmat exec ./my-agent-loop.sh # any agent, any script
hazmat shell # interactive contained shell
| Layer | What it protects |
|-------|-----------------|
| User isolation | Dedicated agent macOS user. Your ~/.ssh, ~/.aws, Keychain — structurally inaccessible |
| Kernel sandbox | Per-session seatbelt policy. Project gets read-write, everything else denied |
| Credential deny | SSH keys, AWS creds, GPG keys, GitHub tokens — blocked at the kernel level, even inside agent home |
| Network firewall | pf rules block SMTP, IRC, FTP, Tor, VPN, and other exfiltration protocols |
| DNS blocklist | Known tunnel/paste/C2 services (ngrok, pastebin, webhook.site) resolve to localhost |
| Supply chain hardening | npm ignore-scripts=true by default — blocks the entire class of postinstall attacks |
| Automatic snapshots | Kopia snapshots before every session — roll back if the agent breaks something |
Docker Projects
Hazmat distinguishes between two Docker shapes:
- Private-daemon Docker projects auto-route into Docker Sandbox mode. The agent runs inside an isolated sandbox with its own private Docker daemon.
- Shared-daemon Docker projects do not. If the repo appears to depend on the host Docker daemon, Hazmat stops and asks you to choose an explicit code-only session (
--docker=none) or move the workflow to Tier 4.
Use hazmat config docker none -C /path/to/project to persist code-only routing for a shared-daemon repo. See docs/tier3-docker-sandboxes.md and docs/shared-daemon-projects.md.
Comparison
| | Built-in sandbox | Agent Safehouse | SandVault | nono | Docker | Hazmat | |---|:---:|:---:|:---:|:---:|:---:|:---:| | Separate user account | — | — | ✓ | — | ✓ | ✓ | | Seatbelt / kernel sandbox | ✓ | ✓ | ✓ | ✓ | n/a | ✓ | | Credential path deny | — | partial | — | — | ✓ | ✓ | | Network firewall (pf) | — | — | — | — | ✓ | ✓ | | DNS blocklist | — | — | — | — | — | ✓ | | Supply chain hardening | — | — | — | — | — | ✓ | | Backup / rollback | — | — | — | ✓ | — | ✓ | | Agent-agnostic | — | ✓ | ✓ | ✓ | ✓ | ✓ | | macOS native | ✓ | ✓ | ✓ | ✓ | — | ✓ |
Quick Start
# Install via Homebrew
brew install dredozubov/tap/hazmat
# Or install from GitHub releases
curl -fsSL https://raw.githubusercontent.com/dredozubov/hazmat/master/scripts/install.sh | bash
# One-time setup (~10 min)
hazmat init --bootstrap-agent claude
# Start working
cd your-project
hazmat claude
hazmat init creates the agent user, configures containment, and sets up automatic snapshots. During interactive setup you can choose to bootstrap Claude Code, Codex, OpenCode, or skip agent installation and add one later with hazmat bootstrap .... It can also seed Claude with portable conveniences from an existing setup while keeping Hazmat in control of runtime and safety settings. Preview first with hazmat init --dry-run.
Daily Workflow
# Claude Code — full autonomy, contained
hazmat claude
hazmat claude -p "refactor the auth module"
hazmat claude -C ~/other-project
# Any command in containment
hazmat exec npm test
hazmat exec python train.py
hazmat exec ./run-agent-loop.sh
# Continue a hazmat Claude session as your normal user
claude --resume "$(hazmat export claude session)" --fork-session
# Interactive shell
hazmat shell
# See what the agent changed
hazmat diff
hazmat snapshots
hazmat restore # undo last session
Extra Directories
The agent can only write to the project directory by default. Expose additional read-only or read-write paths explicitly:
hazmat claude -R ~/workspace/shared-lib -R ~/reference-docs
hazmat claude -W ~/.venvs/my-app
hazmat config access add -C ~/workspace/my-app --read ~/reference-docs --write ~/.venvs/my-app
-R stays read-only. -W adds another explicit writable root for that
project or session. Both are enforced by the kernel sandbox — not advisory.
Session Integrations
hazmat integration list
hazmat integration show node
hazmat claude --integration node
hazmat claude --integration python-uv
hazmat config set integrations.pin "~/workspace/my-app:node,go"
Session integrations are ergonomic overlays for common stacks. They may add
auto-resolved read-only toolchain paths, extend snapshot excludes, and pass
through safe environment selectors like GOPATH or VIRTUAL_ENV. They do
not widen write access, relax the credential deny list, or change the network
policy.
If a repo mixes stacks across subdirectories, add .hazmat/integrations.yaml
to the repo so users do not have to discover nested frontend or TLA+
integrations manually.
Repos can declare recommended integrations in .hazmat/integrations.yaml;
Hazmat prompts once for approval, then reuses that approval until the file
changes. See docs/integrations.md.
Handing a Hazmat Session Back to Host Claude
If you start a conversation inside hazmat claude and later want to continue it outside containment, export it into your normal Claude session store:
claude --resume "$(hazmat export claude session)" --fork-session
claude --resume "$(hazmat export claude session <session-id>)" --fork-session
hazmat export claude session exports the latest hazmat Claude session for the current project by default, or a specific session when you pass an ID. It copies the session bundle into your host ~/.claude/projects/... directory and prints the resume ID on stdout.
Configuration
hazmat config # view everything
hazmat config edit # open config in $EDITOR
hazmat config agent # set API key + git identity
hazmat config import claude # import portable basics from an existing setup
hazmat bootstrap claude # install Claude Code for the agent user
hazmat bootstrap codex # install Codex for the agent user
hazmat config import opencode
