🔒 filesystem isolation + egress control · the model Anthropic uses to contain Claude Code

Run code you don't trust. Leak nothing.

airlock is a disposable, hardened Linux sandbox for your Mac. Code goes in, does its work, and the container is destroyed on exit — without ever exposing your keys, files, or secrets.

// an airlock between sketchy code and your real machine

3isolation modes
27+whitelisted domains
0secrets leaked
--rmthrowaway by default
The problem

Every npm install runs code as you.

Modern development means constantly running code you didn't write — cloning OSS, installing hundreds of transitive dependencies, trying a tool from a random GitHub link. Any one can hide a malicious postinstall script or a compromised package. Install-time lifecycle scripts are the single largest code-execution surface in the npm ecosystem — a single compromised package anywhere in your dependency tree runs arbitrary commands on your machine, with access to ~/.ssh, cloud credentials, browser tokens and .env files.

Attack stepWithout airlockWith airlock
Read your secretsFull home-directory accessOnly the project folder is mounted
Send them outAny internet hostEgress blocked except a domain whitelist
Persist / spreadWrites anywhere on diskRead-only root, throwaway container

airlock removes both halves of the attack — isolation stops the escape, egress control stops the exfiltration. Either one alone is not enough.

In the wild · documented incidents

This isn't hypothetical. It keeps happening.

Real, widely-reported supply-chain attacks against the npm ecosystem — each one ran arbitrary code on developer machines during a routine install. Every source below is a primary advisory or major security-press report. Verify them yourself.

2018~8M downloads affected

event-stream → bitcoin-wallet backdoor

A trusted maintainer handed off the package to a new "contributor," who slipped in an obfuscated dependency (flatmap-stream) that harvested private keys from a specific bitcoin wallet app. It sat undetected for ~2.5 months.

2021~7M downloads / week

ua-parser-js → password stealer + crypto miner

Attackers hijacked the maintainer's npm credentials and published malware that installed a password stealer and a cryptominer on install. The threat was serious enough that CISA issued a public alert.

2022CVE-2022-23812

node-ipc → "protestware" that wiped files

The maintainer shipped code that, on machines geolocated to Russia or Belarus, overwrote files on disk with a heart emoji. node-ipc is a transitive dependency of the Vue CLI — so the blast radius was enormous.

2025~500 packages · 2.6B weekly downloads

Shai-Hulud → the first self-propagating npm worm

Phished maintainer tokens let malware steal cloud + npm secrets, publish them publicly, and republish itself into more packages automatically. CISA issued an ecosystem-wide alert. Install scripts fetched second-stage payloads from attacker infrastructure.

✓ Every link above points to a primary advisory (CISA, npm) or established security press — open the full reference list ↓

Three modes, one image

Pick the lightest mode that covers your threat.

From everyday untrusted installs to genuinely hostile code and autonomous agents — airlock scales the isolation to the risk.

DEFAULT
🔒

run

airlock run [cmd]

UNTRUSTED. OSS you don't trust, sketchy installers, client deploys.

  • boxContainer — shared kernel, fully hardened
  • netWhitelist proxy only — everything else blocked
  • fsOnly the project dir is visible
  • keyClaude token only — no history
  • lifeThrowaway (--rm) — fresh every spin
⚠️

dev

airlock dev [cmd]

TRUSTED. Your own code — a clean, consistent, disposable Linux workstation.

  • boxContainer — same hardening
  • netFull internet — it's your trusted code
  • fsOnly the project dir is visible
  • keyClaude token + full history
  • lifeThrowaway — Linux parity with prod
STRONGEST
🧊

sbx

airlock sbx [cmd]

MICRO-VM. Genuinely hostile code or an autonomous AI agent with shell access.

  • boxmicroVM — its own kernel. Escape-proof
  • netSame default-deny egress whitelist
  • fsProject dir only · no host secrets
  • keyKept & reused — /login persists
  • life--fresh wipes · down destroys

Rule of thumb: default to run for anything you don't trust — light enough for all-day use, even on a 16 GB Air. Escalate to sbx only when a kernel-escape would be catastrophic. Use dev only for code you wrote.

Defense in depth

Hardened to the bone.

run and dev share every one of these guards — defined in docker-compose.yml. sbx adds a separate kernel on top.

👤

Non-root user

Runs as the unprivileged node user (uid 1000) — never root.

🪨

Read-only root FS

The entire root filesystem is immutable. Only tiny RAM-backed tmpfs mounts are writable.

🧹

All capabilities dropped

cap_drop: ALL + no-new-privileges — no privilege escalation.

💨

Ephemeral RAM home

/home/node is a tmpfs, wiped on stop. Nothing persists between spins.

📉

CPU / memory / PID caps

4 CPU · 4 GB RAM · 512 PIDs — fork bombs and runaway builds are blunted.

🗑️

Throwaway container

--rm means the box is gone the instant you type exit.

The same model the pros use

Two boundaries. Both required.

airlock's design mirrors how Anthropic contains Claude Code: filesystem isolation so a compromised process can't read your secrets, plus network isolation through a domain-filtering proxy so it can't exfiltrate them. Their own engineering team is explicit that you need both — without network isolation a compromised agent leaks your SSH keys; without filesystem isolation it escapes and gets network access anyway.

Same two primitives, packaged for your laptop: only the project dir is mounted, and egress runs through a default-deny whitelist (tinyproxy.conf).

the containment model
# boundary 1 — filesystem isolation
read ~/.ssh/id_rsa denied (not mounted)
read /workspace/* ok (your project)

# boundary 2 — network egress control
POST attacker.net 403 blocked by proxy
GET registry.npmjs.org 200 allowed

# neither alone is enough:
no net → escape still leaks via disk
no fs → escape regains the network
both → contained ✓
When to reach for it

Real scenarios, rated by mode.

Each spin is logged out, so you can also deploy for many clients without cross-account mix-ups or credentials left on disk.

📦

Running untrusted open-source

Clone it, poke at it. If it's malware, it can't reach your keys or phone home.
airlock run

🚀

Deploying for clients

Every spin is logged out — vercel login fresh as the right client, no mix-ups.
airlock run

🧪

A sketchy curl | bash installer

That one-liner install script runs contained instead of as you.
airlock run

🐧

Reproducing a Linux-only bug

From your Mac, without dual-booting. Path casing, native modules, line endings.
airlock dev

☠️

Genuinely hostile code / malware

A microVM with its own kernel — even a container-escape 0-day can't reach your Mac.
airlock sbx

🤖

An autonomous AI agent with a shell

Give it room to work behind a hardware-enforced boundary and a tight whitelist.
airlock sbx

Quick start

Two commands. That's the whole flow.

The folder you launch from becomes /workspace — the only host folder the container can ever see.

Bind dev servers to 0.0.0.0

Ports 3000 · 3001 · 5173 · 4000 · 8080 forward to your Mac. Use -H 0.0.0.0 / --host 0.0.0.0.

Need a second terminal?

Don't run run twice. Attach with airlock exec to jump into the same box.

Toolchain included

Node 24.16 · pnpm · npm · nvm · git · ripgrep · Claude Code, plus vercel · netlify · heroku · firebase · wrangler.

~/some/cloned/oss-project — bash
# UNTRUSTED: clone something sketchy, run it safely
$ cd ~/some/cloned/oss-project
$ airlock run

────────────────────────────────────────────
🔒 airlock · UNTRUSTED safe — locked-down
────────────────────────────────────────────
path /Users/you/some/oss-project
↳ the only host folder the box can see
network whitelist only · 27 domains
secrets none of your files · token only
claude logged in (synced from Keychain)
────────────────────────────────────────────

node@airlock:/workspace$ npm install
registry.npmjs.org → 200 allowed
node@airlock:/workspace$ curl https://evil-cdn.net
✗ 403 blocked — not on the whitelist
node@airlock:/workspace$ npm run dev -- -H 0.0.0.0
▲ ready on http://localhost:3000
Reference

Every command.

Pass a one-off command instead of a shell anytime: airlock run npm test, airlock dev claude, airlock exec vercel deploy. Full source: the airlock CLI ↗

Enter a sandbox
airlock run [cmd]🔒 UNTRUSTED mode — no history, whitelist-only network.
airlock dev [cmd]⚠️ TRUSTED mode — Claude login + history, full internet.
airlock sbx [cmd]🧊 STRONGEST — Docker microVM (own kernel) + the same whitelist.
airlock exec [cmd]Open another terminal into the already-running sandbox.
Network
airlock allow <domain>Add a domain to the egress whitelist, then airlock rebuild.
airlock blockedList the URLs/hosts the proxy denied — works even after you exit.
airlock logsFollow proxy logs live — watch what's allowed and blocked.
Lifecycle
airlock sbx --freshDelete this project's old microVM and start a clean one.
airlock sbx downDestroy just this project's microVM.
airlock downStop & remove everything — run/dev containers, proxy, all sbx microVMs.
Manage & info
airlock statusDashboard: images, what's running (🔒 SAFE / ⚠ TRUSTED), and the whitelist.
airlock build / rebuildBuild images — rebuild is from scratch, no cache (apply a new whitelist).
airlock helpShow help — also --help, -h.
Networking

Default-deny. By design.

In run mode the sandbox has no direct internet. It sits on an internal Docker network and can only reach a tiny tinyproxy that forwards a request only if the destination host matches a rule — including HTTPS, by filtering the CONNECT host.

Default whitelist covers npm · yarn · pip · cargo, GitHub · GitLab, common deploy targets (Vercel, Netlify, Heroku, Cloudflare, Firebase) and Anthropic. Edit it in proxy/filter, or:

airlock blocked # lists denied hosts as URLs airlock allow files.example.com airlock rebuild # bake it into the proxy image
airlock — egress proxy
# tinyproxy · FilterDefaultDeny Yes
github.com HTTP 200 (allowed)
registry.npmjs.org HTTP 200 (allowed)
example.com 403 blocked
tracker.evil-test.io 403 blocked

# $ airlock blocked
Denied — not on the whitelist:
https://tracker.evil-test.io
https://some-cdn.attacker.net

Exit run and the proxy + its network are torn down automatically — nothing internet-connected lingers.

Setup · fresh or reset Mac

Up and running in three steps.

Wipe or replace your Mac and you get airlock back the same way. setup.sh is idempotent and safe to re-run.

1

Restore the folder

From backup or git clone. The token re-syncs from your Keychain — it's never backed up.

git clone <your-repo> ~/sandbox/airlock
2

Run setup

Checks Docker, makes the airlock alias, creates .secrets/, builds images, self-tests the whitelist.

bash ~/sandbox/airlock/setup.sh
3

Open a terminal & go

First run pops a Keychain prompt → Always Allow.

cd <a project> && airlock run

Claude login, handled safely

macOS keeps your Claude login in the Keychain, not a file. airlock exports it to a chmod 600, git-ignored secret, mounts it read-only, and copies it into the container's ephemeral home. Token refreshes stay inside the box and are never written back to your Mac. dev mounts your full ~/.claude history; run mounts only the token — see entrypoint.sh.

Security model — and its limits

Honest about the boundary.

airlock raises the cost of an attack significantly — but it's not a guarantee. Here's exactly what it does and doesn't cover.

✓ What it protects against

  • Secret theft. A dependency reading your home directory and keys — only the project is mounted.
  • Exfiltration. Sending data out — egress is whitelist-only and default-deny.
  • Persistence. Read-only root + throwaway container leave nothing behind.
  • Resource exhaustion. CPU / memory / PID caps blunt fork bombs and runaway builds.
  • Kernel escape (sbx). The microVM's own kernel contains even a container-escape 0-day.

! What it does not

  • Shared kernel in run / dev. A kernel-level 0-day would defeat a container. Use sbx for max isolation.
  • The project dir is read-write. Untrusted code can modify the folder you opened — it can't touch anything else.
  • Whitelisted domains are trusted. GitHub / npm can host arbitrary content. Keep the list tight.
  • The Claude token is present in run. Set AIRLOCK_SEED_CLAUDE=0 if that matters; revoke with /logout.
  • dev is not for untrusted code. Full internet + your history. Use run or sbx.

What's in the box

One host CLI plus Docker plumbing — auditable end to end. Each file links to its source on GitHub.

syedtasavour/airlock/
├── airlock # the host CLI (aliased as `airlock`)
├── setup.sh # one-shot restore on a fresh/reset Mac
├── Dockerfile # sandbox image (Node + deploy CLIs + Claude)
├── docker-compose.yml # run/dev profiles + proxy + networks + limits
├── entrypoint.sh # seeds Claude login (token-only in run)
├── proxy/
│   ├── tinyproxy.conf # default-deny egress config
│   └── filter # the domain whitelist (`airlock allow`)
├── .secrets/ # exported Claude token (git-ignored, 600)
└── README.md
References · verify everything

Sources & further reading.

Primary advisories and reporting from established platforms — government (CISA), the npm registry itself, Anthropic's engineering team, and major security press. Nothing here is second-hand.

Links open on the original publishers' sites. airlock is independent and not affiliated with, or endorsed by, any organization listed above.

Ship safer, today

Stop running strangers' code
as yourself.

Clone it. airlock run. Poke at it. If it's malware, it can't reach your keys or phone home. Then it's gone.