# hazmat

Hazmat is a macOS containment system that runs AI coding agents (Claude Code, OpenCode, Codex) inside OS-level isolation. Instead of relying on permission prompts or [[human-in-the-loop]] confirmation dialogs, it wraps the agent session in three enforcement layers and lets it work autonomously within a locked-down perimeter.

## How it works

Containment is built from three independent layers, each compensating for gaps in the others:

**Unix user isolation.** The agent runs as a dedicated macOS user created during `hazmat init`. The host's home directory is structurally inaccessible — not hidden by policy, but unreachable by the OS user model. This is the coarsest layer but the hardest to escape.

**Seatbelt kernel sandbox.** Each session gets a per-session Seatbelt profile with a default-deny posture. The project directory gets read-write access; toolchain paths get read-only. Credential paths (SSH keys, AWS credentials, GPG keyring) are denied at kernel level even within the agent's own home directory. Seatbelt is Apple's undocumented sandboxing framework — the same one App Store apps use — so it operates below userspace.

**pf firewall.** A packet filter ruleset blocks outbound SMTP, IRC, FTP, Tor, and VPN protocols to prevent data exfiltration. A DNS blocklist resolves known C2 and tunneling services (ngrok, pastebin, webhook.site) to localhost. HTTPS on port 443 cannot be blocked without breaking the agent's ability to call APIs, so this remains an acknowledged gap.

On top of these layers, hazmat sets npm `ignore-scripts=true` to block postinstall supply-chain attacks, and takes automatic Kopia snapshots before each session for rollback.

## Usage

```bash
# one-time setup — creates agent user and security infra
hazmat init

# import existing Claude Code config
hazmat config import claude

# install agent tooling for the contained user
hazmat bootstrap claude

# run a contained session
hazmat claude

# or run arbitrary commands contained
hazmat exec <command>

# audit and recover
hazmat diff          # what changed this session
hazmat snapshots     # list snapshots
hazmat restore       # roll back
hazmat explain       # preview session contract before running
```

Access control flags: `-R` for persistent read-only paths, `-W` for additional writable roots beyond the project directory. Per-project config lives in `.hazmat/config.yaml`.

Before a session starts, hazmat displays a "contract" showing containment mode (native or Docker sandbox), project read-write paths, auto-detected read-only paths, and snapshot config.

## Formal verification

Setup ordering, Seatbelt policy structure, backup safety, and tier-2/tier-3 policy equivalence are formally verified with TLA+ specifications. The repo also documents 16 CVEs in `docs/cve-audit.md` and has a full threat model in `threat-matrix.md`.

## Limitations

- macOS only (Linux port is an open project)
- HTTPS exfiltration can't be blocked — the agent can curl any URL on port 443
- Seatbelt is undocumented by Apple; this is defense-in-depth, not VM-level isolation
- Shared `/tmp` is accessible to other processes

## See also

For a Linux approach with finer-grained control, see [[syscall-binary-rewriting]] — binary rewriting that replaces syscall instructions with traps, enabling inspection and modification of syscall arguments rather than just allow/deny. For the Linux equivalent of hazmat's filesystem-isolation layer, see [[bubblewrap-dev-env]] (Ciężarkiewicz's bubblewrap-based setup). The broader taxonomy covering all four sandboxing layers — filesystem, network, HTTP-policy, syscall — is in [[sandboxing-ai-agents]]; [[crabtrap]] fills the HTTP-policy layer that hazmat's pf firewall can't cover.

## Links

- Repo: https://github.com/dredozubov/hazmat
- Install: Homebrew tap or GitHub release script
