{"id":48220169,"url":"https://github.com/dredozubov/hazmat","last_synced_at":"2026-04-10T19:06:37.215Z","repository":{"id":347336443,"uuid":"1193685928","full_name":"dredozubov/hazmat","owner":"dredozubov","description":"macOS containment for AI agents — user isolation, kernel sandbox, pf firewall, DNS blocklist, backup/rollback. TLA+ verified.","archived":false,"fork":false,"pushed_at":"2026-04-04T10:07:32.000Z","size":7691,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-04T11:29:25.491Z","etag":null,"topics":["ai-agents","claude-code","cli","containment","developer-tools","formal-verification","go","macos","pf-firewall","sandbox","seatbelt","security","tla-plus"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dredozubov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"ko_fi":"denisredozubov"}},"created_at":"2026-03-27T13:31:02.000Z","updated_at":"2026-04-04T10:07:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dredozubov/hazmat","commit_stats":null,"previous_names":["dredozubov/hazmat"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dredozubov/hazmat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredozubov%2Fhazmat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredozubov%2Fhazmat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredozubov%2Fhazmat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredozubov%2Fhazmat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dredozubov","download_url":"https://codeload.github.com/dredozubov/hazmat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredozubov%2Fhazmat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31409471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude-code","cli","containment","developer-tools","formal-verification","go","macos","pf-firewall","sandbox","seatbelt","security","tla-plus"],"created_at":"2026-04-04T19:08:52.576Z","updated_at":"2026-04-04T19:08:53.407Z","avatar_url":"https://github.com/dredozubov.png","language":"Go","funding_links":["https://ko-fi.com/denisredozubov"],"categories":["Go","Sandboxing \u0026 Isolation"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"assets/hazmat-final.png\" alt=\"Hazmat\" width=\"400\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eHazmat\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eFull autonomy. Controlled environment.\u003c/strong\u003e\u003cbr\u003e\n  OS-level containment for AI coding agents on macOS\n\u003c/p\u003e\n\n---\n\nAI 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.\n\nHazmat makes that safe.\n\n```bash\nhazmat claude                     # Claude Code with full containment\nhazmat opencode                   # OpenCode with full containment\nhazmat exec ./my-agent-loop.sh    # any agent, any script\n```\n\nOne 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.\n\n## What You See\n\nEvery session starts with a contract — a plain-language summary of what the agent can and can't do:\n\n```\nhazmat: session\n  Mode:                 Native containment\n  Why this mode:        using native containment because no Docker requirement was detected\n  Project (read-write): /Users/dr/workspace/my-app\n  Integrations:         go\n  Auto read-only:       /Users/dr/go/pkg/mod\n  Read-only extensions: none\n  Read-write extensions: none\n  Service access:       none\n  Pre-session snapshot: on\n  Snapshot excludes:    vendor/\n```\n\nIf the project looks compatible with a private Docker daemon, Hazmat switches modes automatically:\n\n```\nhazmat: session\n  Mode:                 Docker Sandbox\n  Why this mode:        using Docker Sandbox because this project appears compatible with a private Docker daemon (Dockerfile)\n  Project (read-write): /Users/dr/workspace/api-service\n  Integrations:         node\n  Auto read-only:       none\n  Read-only extensions: none\n  Read-write extensions: none\n  ...\n```\n\nPreview any session before running it with `hazmat explain`.\n\n## The Problem\n\n`--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.\n\nBut the built-in protections aren't enough:\n\n- **Agents actively reason about escaping.** Ona's research showed Claude Code [bypassing its own denylist](https://ona.com/stories/how-claude-code-escapes-its-own-denylist-and-sandbox) via `/proc/self/root` path traversal, then attempting to disable bubblewrap when that was caught.\n- **[16 Claude Code CVEs](docs/cve-audit.md) and counting.** [CVE-2025-59536](https://nvd.nist.gov/vuln/detail/CVE-2025-59536): RCE through project config files. [CVE-2026-25725](https://advisories.gitlab.com/pkg/npm/@anthropic-ai/claude-code/CVE-2026-25725/): sandbox escape via `settings.json` injection. [CVE-2026-21852](https://nvd.nist.gov/vuln/detail/CVE-2026-21852): API key exfiltration before the trust prompt appeared.\n- **Supply chain attacks execute instantly.** The [axios npm compromise](https://github.com/axios/axios/issues/10604) (2026) delivered a RAT through a `postinstall` hook in 2 seconds — before `npm install` even finished. The [s1ngularity attack](https://www.wiz.io/blog/s1ngularity-supply-chain-attack) weaponized Claude Code itself to steal credentials.\n\nNo 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.\n\n## What Hazmat Does\n\n```bash\nhazmat claude                     # Claude Code with full autonomy\nhazmat exec ./my-agent-loop.sh    # any agent, any script\nhazmat shell                      # interactive contained shell\n```\n\n| Layer | What it protects |\n|-------|-----------------|\n| **User isolation** | Dedicated `agent` macOS user. Your `~/.ssh`, `~/.aws`, Keychain — structurally inaccessible |\n| **Kernel sandbox** | Per-session [seatbelt](https://developer.apple.com/documentation/security) policy. Project gets read-write, everything else denied |\n| **Credential deny** | SSH keys, AWS creds, GPG keys, GitHub tokens — blocked at the kernel level, even inside agent home |\n| **Network firewall** | `pf` rules block SMTP, IRC, FTP, Tor, VPN, and other exfiltration protocols |\n| **DNS blocklist** | Known tunnel/paste/C2 services (ngrok, pastebin, webhook.site) resolve to localhost |\n| **Supply chain hardening** | npm `ignore-scripts=true` by default — blocks the entire class of postinstall attacks |\n| **Automatic snapshots** | Kopia snapshots before every session — roll back if the agent breaks something |\n\n### Docker Projects\n\nHazmat distinguishes between two Docker shapes:\n\n- **Private-daemon Docker projects** auto-route into Docker Sandbox mode. The agent runs inside an isolated sandbox with its own private Docker daemon.\n- **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.\n\nUse `hazmat config docker none -C /path/to/project` to persist code-only routing for a shared-daemon repo. See [docs/tier3-docker-sandboxes.md](docs/tier3-docker-sandboxes.md) and [docs/shared-daemon-projects.md](docs/shared-daemon-projects.md).\n\n### Comparison\n\n| | [Built-in sandbox](https://github.com/anthropic-experimental/sandbox-runtime) | [Agent Safehouse](https://github.com/eugene1g/agent-safehouse) | [SandVault](https://github.com/webcoyote/sandvault) | [nono](https://github.com/always-further/nono) | [Docker](https://docs.docker.com/ai/sandboxes/) | **Hazmat** |\n|---|:---:|:---:|:---:|:---:|:---:|:---:|\n| Separate user account | — | — | ✓ | — | ✓ | ✓ |\n| Seatbelt / kernel sandbox | ✓ | ✓ | ✓ | ✓ | n/a | ✓ |\n| Credential path deny | — | partial | — | — | ✓ | ✓ |\n| Network firewall (pf) | — | — | — | — | ✓ | ✓ |\n| DNS blocklist | — | — | — | — | — | ✓ |\n| Supply chain hardening | — | — | — | — | — | ✓ |\n| Backup / rollback | — | — | — | ✓ | — | ✓ |\n| Agent-agnostic | — | ✓ | ✓ | ✓ | ✓ | ✓ |\n| macOS native | ✓ | ✓ | ✓ | ✓ | — | ✓ |\n\n## Quick Start\n\n```bash\n# Install via Homebrew\nbrew install dredozubov/tap/hazmat\n\n# One-time setup (~10 min)\nhazmat init --bootstrap-agent claude\n\n# Start working\ncd your-project\nhazmat claude\n```\n\n`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`.\n\n## Daily Workflow\n\n```bash\n# Claude Code — full autonomy, contained\nhazmat claude\nhazmat claude -p \"refactor the auth module\"\nhazmat claude -C ~/other-project\n\n# Any command in containment\nhazmat exec npm test\nhazmat exec python train.py\nhazmat exec ./run-agent-loop.sh\n\n# Continue a hazmat Claude session as your normal user\nclaude --resume \"$(hazmat export claude session)\" --fork-session\n\n# Interactive shell\nhazmat shell\n\n# See what the agent changed\nhazmat diff\nhazmat snapshots\nhazmat restore          # undo last session\n```\n\n### Extra Directories\n\nThe agent can only write to the project directory by default. Expose\nadditional read-only or read-write paths explicitly:\n\n```bash\nhazmat claude -R ~/workspace/shared-lib -R ~/reference-docs\nhazmat claude -W ~/.venvs/my-app\nhazmat config access add -C ~/workspace/my-app --read ~/reference-docs --write ~/.venvs/my-app\n```\n\n`-R` stays read-only. `-W` adds another explicit writable root for that\nproject or session. Both are enforced by the kernel sandbox — not advisory.\n\n### Session Integrations\n\n```bash\nhazmat integration list\nhazmat integration show node\nhazmat claude --integration node\nhazmat claude --integration python-uv\nhazmat config set integrations.pin \"~/workspace/my-app:node,go\"\n```\n\nSession integrations are ergonomic overlays for common stacks. They may add\nauto-resolved read-only toolchain paths, extend snapshot excludes, and pass\nthrough safe environment selectors like `GOPATH` or `VIRTUAL_ENV`. They do\nnot widen write access, relax the credential deny list, or change the network\npolicy.\n\nIf a repo mixes stacks across subdirectories, add `.hazmat/integrations.yaml`\nto the repo so users do not have to discover nested frontend or TLA+\nintegrations manually.\n\nRepos can declare recommended integrations in `.hazmat/integrations.yaml`;\nHazmat prompts once for approval, then reuses that approval until the file\nchanges. See [docs/integrations.md](docs/integrations.md).\n\n### Handing a Hazmat Session Back to Host Claude\n\nIf you start a conversation inside `hazmat claude` and later want to continue it outside containment, export it into your normal Claude session store:\n\n```bash\nclaude --resume \"$(hazmat export claude session)\" --fork-session\nclaude --resume \"$(hazmat export claude session \u003csession-id\u003e)\" --fork-session\n```\n\n`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.\n\n## Configuration\n\n```bash\nhazmat config                                        # view everything\nhazmat config edit                                   # open config in $EDITOR\nhazmat config agent                                  # set API key + git identity\nhazmat config import claude                          # import portable basics from an existing setup\nhazmat bootstrap claude                              # install Claude Code for the agent user\nhazmat bootstrap codex                               # install Codex for the agent user\nhazmat config import opencode                        # import portable OpenCode basics from an existing setup\nhazmat bootstrap opencode                            # install OpenCode for the agent user\nhazmat opencode                                      # launch OpenCode in containment\nhazmat integration list                              # inspect built-in and user integrations\nhazmat config set integrations.pin \"~/workspace/app:node,go\" # auto-activate integrations for a project\nhazmat config access add -C ~/workspace/app --write ~/.venvs/app # persist project read/write extensions\nhazmat config docker none -C ~/workspace/app         # persist code-only mode for a shared-daemon repo\nhazmat config cloud                                  # set up S3 backup\nhazmat config set session.skip_permissions false      # re-enable Claude's permission prompts\nhazmat config set backup.retention.keep_latest 30     # change snapshot retention\n```\n\nAll settings live in `~/.hazmat/config.yaml`.\n\nPortable import keeps Hazmat's runtime and safety config separate from whatever you use outside containment. See [docs/claude-import.md](docs/claude-import.md) for the current import rules and non-goals.\nOpenCode follows the same curated story; see [docs/opencode-import.md](docs/opencode-import.md).\nSession integrations are documented in [docs/integrations.md](docs/integrations.md).\n\n## Architecture\n\n```\n  You (dr)                          Agent (agent)\n  ────────                          ─────────────\n  ~/                                /Users/agent/\n  ~/.ssh, ~/.aws  ← denied →       ~/.claude/\n  ~/workspace/    ← shared →        ~/workspace/ (symlink)\n\n  hazmat claude\n       │\n       ├── snapshot project (Kopia)\n       ├── generate per-session seatbelt policy\n       ├── sudo -u agent hazmat-launch \u003cpolicy\u003e\n       ├── sandbox_init() (kernel sandbox)\n       └── exec claude --dangerously-skip-permissions\n```\n\nThree OS-level enforcement layers:\n1. **Unix user** — the agent runs as a different user. Your home directory is structurally inaccessible.\n2. **Seatbelt** — kernel-level filesystem policy. Default deny, explicit allows for project and toolchain paths.\n3. **pf firewall** — packet filter rules scoped to `user agent`. Blocks dangerous protocols.\n\nSetup ordering, seatbelt policy structure, backup safety, version migration,\nsession-time host permission repairs, harness lifecycle state, Tier 3 launch\ncontainment, and Tier 2/Tier 3 core policy equivalence are\n[formally verified with TLA+](tla/VERIFIED.md).\n\n## Undo Everything\n\n```bash\nhazmat rollback                               # remove all system config\nhazmat rollback --delete-user --delete-group   # also delete agent account\n```\n\nYour project files are not touched.\n\n## Honest Limitations\n\n- **Seatbelt is defense-in-depth.** Apple's SBPL is undocumented. It prevents accidents and blocks credential access, but is not a VM-level boundary.\n- **HTTPS exfiltration is not blocked.** The agent can `curl` any URL on port 443. The DNS blocklist catches known-bad domains but not novel ones.\n- **macOS only.** No Linux (yet). The containment primitives are macOS-specific.\n- **Shared `/tmp`.** The agent can read temp files from other processes.\n\nFor the full threat model, see [threat-matrix.md](docs/threat-matrix.md). For stronger isolation, see [tier4-vm-isolation.md](docs/tier4-vm-isolation.md).\n\n## Documentation\n\n| Doc | What it covers |\n|-----|---------------|\n| [usage.md](docs/usage.md) | Complete user guide |\n| [claude-import.md](docs/claude-import.md) | Portable Claude basics import: scope, conflicts, and non-goals |\n| [opencode-import.md](docs/opencode-import.md) | Portable OpenCode basics import: scope, conflicts, and non-goals |\n| [integrations.md](docs/integrations.md) | Session integrations: activation, project extensions, repo recommendations, trust model |\n| [testing.md](docs/testing.md) | Test suite map: local loops, e2e scripts, CI, and VM-backed verification |\n| [tier3-docker-sandboxes.md](docs/tier3-docker-sandboxes.md) | Docker Sandbox mode: setup, network policy, Compose hardening |\n| [cve-audit.md](docs/cve-audit.md) | How hazmat defends against every known Claude Code CVE |\n| [threat-matrix.md](docs/threat-matrix.md) | Risk-by-risk coverage analysis |\n| [design-assumptions.md](docs/design-assumptions.md) | Every non-obvious design decision |\n| [brief-supply-chain-hardening.md](docs/brief-supply-chain-hardening.md) | Supply chain attack analysis and mitigations |\n| [tla/VERIFIED.md](tla/VERIFIED.md) | TLA+ formal verification specs |\n\n## Contributing\n\nHazmat is early. The UX, security model, and documentation are all actively evolving. Feedback is the most valuable contribution right now.\n\n**Ways to help:**\n\n- **Try it and tell us what broke.** [Open an issue](https://github.com/dredozubov/hazmat/issues) with your macOS version and what happened. Rough bug reports are fine.\n- **Tell us what's confusing.** If a prompt didn't make sense, a command did something unexpected, or the docs left you guessing — that's a bug.\n- **Security review.** If you find a containment bypass, credential leak, or policy gap — please report it. We take these seriously.\n- **Linux port.** The architecture is OS-agnostic (user isolation + kernel sandbox + firewall). The primitives are different (namespaces, seccomp, nftables). This is the biggest open project.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for build instructions and PR guidelines.\n\n## License\n\nMIT\n\n---\n\n\u003csub\u003eThe Simpsons and all related characters are property of 20th Television and The Walt Disney Company. The Claude logo is property of Anthropic. We do not claim any rights to these properties. Their use here is purely for entertainment purposes.\u003c/sub\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdredozubov%2Fhazmat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdredozubov%2Fhazmat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdredozubov%2Fhazmat/lists"}