Sandbox your code to protect against dependency attacks, and let your AI agent work on its own.
Uses Lima to create lightweight Debian VMs on macOS and Linux. Ships with dev tools, Docker, and a headless Chrome browser with Chrome DevTools MCP pre-configured.
Supports Claude Code, OpenCode, and Codex CLI out of the box. Other agents can be run via devm shell.
Never install attack vectors such as npm, claude or even Docker on your host machine again!
- macOS or Linux
- Lima (installed automatically via Homebrew if available)
- A subscription or API key for your agent of choice
Option 1: Git clone (lets you pull updates with git pull)
git clone https://github.com/sneko/devm.git ~/devm
echo 'alias devm="~/devm/devm.sh"' >> ~/.zshrc # or ~/.bashrc for bashOption 2: Direct download (no git needed, also allowing updates by overriding downloaded file)
mkdir -p ~/devm && wget -qO ~/devm/devm.sh https://raw.githubusercontent.com/sneko/devm/main/devm.sh && chmod +x ~/devm/devm.sh
echo 'alias devm="~/devm/devm.sh"' >> ~/.zshrc # or ~/.bashrc for bashThen reload your shell (source ~/.zshrc) or open a new terminal.
devm setupCreates a base VM template with dev tools, Docker, Chromium, and AI coding agents pre-installed.
Options:
| Flag | Description | Default |
|---|---|---|
--disk GB |
VM disk size in GB | 10 |
--memory GB |
VM memory in GB | 2 |
--cpus N |
Number of CPUs | 1 |
devm setup --disk 50 --memory 16 --cpus 8 # Larger VM for heavy workloadsdevm create myapp ~/code/myapp
devm create myapp ~/code/myapp --cpus 4 --memory 8 --ports 3000,8080
devm create fullstack ~/code/frontend ~/code/backend ~/shared/libs:rwThis saves a named VM definition to ~/.devmconfig. You can mount multiple directories, specify resource limits, forward ports, and pass environment variables.
devm shell myappCreates the VM (or reuses it if it already exists), mounts your directories, and drops you into a zsh shell. The VM persists after you exit.
Inside the VM, claude and codex are aliased with their respective auto-approve flags:
clauderuns with--dangerously-skip-permissionscodexruns with--full-auto
devm run myapp npm install
devm run myapp claude -p "fix all lint errors"
devm run myapp docker compose up -ddevm status # Show status of all VMs
devm stop myapp # Stop the VM (can be restarted later)
devm rm myapp # Destroy the VM (keeps config in ~/.devmconfig)
devm rm myapp --forget # Destroy the VM and remove config entry
devm destroy-all # Destroy all devm VMsTo automatically destroy a VM after the command exits:
devm --rm shell myapp # Shell, then destroy the VM
devm --rm run myapp npm test # Run tests, then destroy the VMTo resize an existing VM, pass resource flags — the config is updated and applied:
devm --cpus 8 --memory 16 shell myappRunning devm setup again updates the base template but does not update existing VMs. Use --reset to re-clone:
devm --reset shell myapp # Destroy and re-clone VM, then open shelldevm --offline shell myapp # Block outbound internet accessBlocks outbound internet from the VM using iptables while preserving host/VM communication (mounts, port forwarding). Useful for ensuring agents don't phone home or download unexpected packages.
devm provision myapp # Re-run the provisioning scriptUpdates Claude Code, OpenCode, Codex CLI, and mise inside an existing VM without rebuilding it.
Each section defines a named VM:
[myapp]
cpus=2
memory=4
disk=10
ports=3000,8080
env.ANTHROPIC_API_KEY
env.DB_URL=postgres://localhost/mydb
mount=~/code/myapp
[fullstack]
cpus=4
memory=8
disk=20
ports=3000,5432,8080
env.OPENAI_API_KEY
mount=~/code/frontend
mount=~/code/backend
mount=~/shared/libs:rw
mount=~/reference/docsSettings:
cpus=N— Number of CPUsmemory=N— Memory in GiBdisk=N— Disk in GiBports=P1,P2,...— Ports to forward (supports ranges:7200-7206)
Environment variables (env.*):
env.VAR— Forward from host environmentenv.VAR=value— Hardcoded valueenv.VAR=— Empty string
Mounts (mount=):
mount=/path— Smart default (git repos writable, others read-only)mount=/path:rw— Force writablemount=/path:ro— Force read-only
Paths in the VM are under /devm/<absolute-host-path>, e.g. ~/code/myapp becomes /devm/Users/you/code/myapp.
Edit ~/.devmconfig directly to customize or backup your configuration.
Create this file to install extra tools into the base VM template. It runs once during devm setup:
# ~/.devm/setup.sh
sudo apt-get install -y postgresql-client
pip install pandas numpyCreate this file to run commands inside every VM on each start:
# ~/.devm/runtime.sh
export MY_API_KEY="..."Create this file at the root of any mounted directory. It runs inside the VM each time it starts:
# your-project/.devm.runtime.sh
npm install
docker compose up -dThe base VM comes with Chrome DevTools MCP pre-configured for Claude and OpenCode, giving agents headless browser access.
To add more MCP servers, add them to ~/.claude.json in your ~/.devm/setup.sh, or edit directly inside a VM via devm shell.
devm setupcreates a Debian 13 VM with Lima, installs system packages (Docker, Chromium, Node.js, etc.) and dev tools (Claude Code, OpenCode, Codex CLI, mise), then stops it as a reusable base templatedevm shell <name>clones the base template into a named VM, applies mounts/ports/resources from~/.devmconfig, runs runtime scripts, and drops you into a shell- The VM persists after exit. Running
devm shellordevm runwith the same name reuses the same VM - Use
devm stopto stop ordevm rmto delete. Use--rmto auto-delete after exit
| Category | Packages |
|---|---|
| Core | git, curl, wget, jq, build-essential, unzip, zip |
| Python | python3, pip, venv |
| Node.js | Node.js 24 LTS (via NodeSource) |
| Version manager | mise (manages .tool-versions, .nvmrc, .python-version, etc.) |
| Search | ripgrep, fd-find |
| Shell | zsh with syntax highlighting and autosuggestions |
| Utilities | htop, GitHub CLI (gh) |
| Browser | Chromium (headless), xvfb |
| Containers | Docker Engine, Docker Compose |
| AI | Claude Code, OpenCode, Codex CLI, Chrome DevTools MCP server |
AI coding agents need full permissions to be useful — they install dependencies, run builds, execute tests, start servers. But running npm install or pip install means executing arbitrary third-party code on your machine.
This is not a theoretical risk. The Shai-Hulud worm compromised thousands of npm packages in 2025 by injecting malicious code that runs during npm install. It harvested npm tokens, GitHub PATs, SSH keys, and cloud credentials from developers' machines.
An AI agent running with --dangerously-skip-permissions on your host would give such an attack full access to everything: your SSH keys, your cloud credentials, your browser sessions, your entire filesystem.
devm runs all code inside the VM. The VM has no access to your SSH keys, npm tokens, cloud credentials, git config, browser sessions, or anything else on your host. If a supply chain attack executes inside the VM, it finds nothing to steal and nowhere to spread.
- Selective port forwarding: Only ports listed in
ports=are forwarded. Auto-forwarding is disabled - Environment whitelisting: Only
env.*entries in the config are passed to the VM - Symlink sandboxing: Symlinks inside mounts cannot escape to the host (
sshfs.followSymlinks=false) - SSH agent disabled: The VM cannot access host SSH credentials
- Git protection:
.gitdirectories are bind-mounted read-only in writable repos - Smart mount defaults: Non-git folders are read-only by default
- Dependency overlays: Host
node_modules,vendor,.venv, etc. are hidden behind VM-local overlays so the agent installs fresh, architecture-correct dependencies
| No sandbox | Docker | VM (devm) | |
|---|---|---|---|
| Agent can run any command | Yes | Yes | Yes |
| File system isolation | None | Partial (shared kernel) | Full |
| Network isolation | None | Partial | Optional (--offline) |
| Can run Docker inside | Yes | Requires DinD or socket mount | Yes (native) |
| Kernel-level isolation | None | None (shares host kernel) | Full (separate kernel) |
| Protection from container escapes | None | None | Yes |
| Browser / GUI tools | Host only | Complex setup | Built-in (headless Chromium) |
Docker containers share the host kernel. A motivated attacker could exploit kernel vulnerabilities to escape. A VM runs its own kernel — even root access inside the VM can't reach the host.
This project was inspired by and forked from agent-vm by Sylvain Zimmer.
Key differences from agent-vm:
- Named VMs with a config file (
~/.devmconfig) instead of one-VM-per-directory - Multi-directory mounts — mount several folders at different locations into one VM
- Explicit port forwarding — only whitelisted ports are forwarded (auto-forwarding disabled)
- Environment variable forwarding — selectively pass host env vars to the VM
- Security hardening — symlink escape prevention, SSH agent disabled, .git read-only by default, dependency overlays
- Provisioning — update dev tools in a running VM without rebuilding (
devm provision) - Single file — setup script is embedded in
devm.sh, no separate file needed - mise for version management instead of pinning Node.js versions
- Shell improvements — zsh with syntax highlighting, autosuggestions, persistent history
MIT