A CLI tool that runs npm/pnpm/bun projects inside a Lima VM on macOS. The VM has no access to your home directory, SSH keys, or anything else on your host. Only directories you explicitly mount get exposed — the project you pass on the command line, plus any extras declared in airlock.toml.
Built for two problems:
- Vetting npm packages before trusting them with your filesystem
- Running dev environments in isolation so a compromised dependency can't reach beyond your project
One-liner:
curl -fsSL https://raw.githubusercontent.com/muneebs/airlock/main/install.sh | bashThis installs lima, jq, yq, and the airlock CLI to ~/.local/bin.
From source:
git clone https://github.com/muneebs/airlock
cd airlock
bash install.shRequirements: macOS (Apple Silicon or Intel). yq v4 is only required if you use airlock.toml.
# Create the VM (one-time — installs Node.js 22, pnpm, bun, Claude Code)
airlock setup
# Audit a project's dependencies without running any install scripts
airlock npm ./my-project
# Mount a project and start developing inside the VM
airlock pnpm ./my-project dev
# Run Claude Code airlocked to a single project directory
airlock claude ./my-projectNo host mounts. Copies package.json and the lockfile into the VM, installs without lifecycle scripts, runs npm audit, and shows which packages have install scripts. Nothing from the VM can touch your host filesystem.
airlock npm ./sketchy-libSame isolation as audit, but installs with lifecycle scripts enabled. After install, the network gets locked (iptables DROP on OUTPUT). You get dropped into a shell to observe what the scripts did and whether anything tries to phone home.
airlock npm ./sketchy-lib fullMounts your project directory into the VM read-write via virtiofs. Changes sync both ways in real time. Ports 3000-9999 are forwarded to localhost by default.
airlock pnpm ./my-app dev
airlock pnpm ./my-app dev 8080:8080 # custom port range
airlock pnpm ./my-app dev 3000:3000 "pnpm dev" # run command immediatelyMultiple projects can be mounted simultaneously — each gets its own path at /home/airlock/projects/<name>:
airlock pnpm ./frontend dev
airlock pnpm ./api dev 8080:8080
airlock shell
# cd projects/frontend && pnpm dev
# cd ../api && pnpm test
airlock remove api # unmount when doneAdding a project restarts the VM (~10 seconds) since Lima can't hot-add mounts.
Mounts the project directory and runs Claude Code inside the VM. Network stays open (Claude Code needs API access). Auth credentials are copied from your host ~/.claude into the VM, but the VM can't modify your host auth config.
Claude Code can read and write files in the mounted project. It has zero access to the rest of your filesystem.
airlock claude .
airlock claude ./my-app
airlock claude ./my-app --print "fix the failing tests"Create an airlock.toml in your project directory to set defaults for ports, startup command, extra mounts, and VM resources. All fields are optional — omit any you don't need.
[vm]
cpu = 2
memory = "4GiB"
disk = "20GiB"
node_version = 22
[dev]
ports = "3000:3000"
command = "pnpm dev"
[services]
compose = "./docker-compose.yml" # started automatically before dev command
[[mounts]]
path = "./api"
[[mounts]]
path = "./shared"
writable = false # read-only (default: true)
[[mounts]]
path = "./data"
writable = true
inotify = true # file-watch events in VM (Lima ≥ 0.21, default: false)Mount options:
| Field | Default | Description |
|---|---|---|
path |
— | Required. Path relative to airlock.toml |
writable |
true |
false = read-only inside VM |
inotify |
false |
true = enable inotify file-watch events (Lima ≥ 0.21). Needed for hot-reload tools that watch via inotify rather than polling |
When [services] compose is set, airlock dev runs docker compose up -d inside the VM before starting your app. Services are reachable from your app code at localhost:<port> as normal. To also reach a service from your host (e.g. a DB client), include its port in [dev] ports.
CLI arguments override config values, which override built-in defaults:
CLI arg > airlock.toml > built-in default
Monorepos: use airlock.toml instead of long CLI invocations:
[dev]
ports = "3000:9999"
command = "pnpm dev"airlock pnpm ./my-monorepo devpnpm workspaces, turborepo, and nx all work since the entire repo is mounted.
airlock lock # block all outbound traffic (DNS still works)
airlock unlock # re-enable outboundfull mode locks the network automatically after install. dev and claude modes leave it open.
airlock setup Create and provision the VM
airlock <npm|pnpm|bun> <dir> Audit (isolated, no host mounts)
airlock <npm|pnpm|bun> <dir> full Install with scripts + locked network
airlock <npm|pnpm|bun> <dir> dev Dev mode (mount + ports)
airlock <npm|pnpm|bun> <dir> dev <ports> Custom port range
airlock <npm|pnpm|bun> <dir> dev <ports> <cmd> Dev mode + run command immediately
airlock claude <dir> Run Claude Code (airlocked)
airlock claude <dir> <args> Run Claude Code with arguments
airlock list Show mounted projects
airlock remove <name> Unmount a project
airlock shell Shell into the VM (projects dir)
airlock run <command> Run a command in the projects dir
airlock lock Block outbound network
airlock unlock Re-enable outbound network
airlock status VM status, mounts, network state
airlock stop Stop the VM
airlock reset Reset to clean baseline (clears all mounts)
airlock destroy Delete everything
airlock version Print version
airlock setup creates an Ubuntu 24.04 VM using Apple's Virtualization.framework and installs:
- Node.js 22 LTS (configurable via
airlock.toml) - pnpm, bun
- Docker CE (with
docker composev2) - Claude Code
- git, curl, jq, iptables
bun and Claude Code installs are non-fatal. If they fail, npm and pnpm still work.
Lima creates a lightweight Linux VM on macOS using Apple's Hypervisor.framework — real hypervisor-level isolation, not container-level process isolation like Docker.
airlock manages the VM lifecycle and a mount registry at ~/.airlock/mounts.json. When you add or remove projects, it rewrites ~/.lima/npm-airlock/lima.yaml and restarts the VM. Host directories are mounted via virtiofs at their original paths inside the VM, with symlinks at /home/airlock/projects/<name> for convenience.
A clean baseline is saved at setup time. airlock reset restores the VM to that state and clears all mounts.
~/.local/bin/airlock The CLI script
~/.lima/npm-airlock/ Lima VM instance
~/.lima/npm-airlock-clean/ Clean baseline for reset
~/.airlock/mounts.json Mount registry
<project>/airlock.toml Optional per-project config
bun-security-scanner — a Bun security scanner that checks dependencies against Google's OSV database before they get installed. Runs transparently on every bun install via bunfig.toml. Complements airlock's VM-level isolation with package-level vulnerability scanning at install time.
# bunfig.toml
[install.security]
scanner = "@nebzdev/bun-security-scanner"- macOS only (Lima with vz requires Apple's Virtualization.framework)
- Adding/removing mounts requires a VM restart (~10s)
- The
airlockuser's UID is mapped to your host UID so file permissions work, but this can conflict if the Lima default user already has that UID auditandfullmodes copy onlypackage.json, the lockfile, and config files — not source code. These modes are for package vetting, not running the project.airlock.tomlrequires yq v4 (mikefarah). If yq is missing and a config file is present, airlock exits with an error pointing toinstall.sh.