Skip to content

sneko/devm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

devm

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!

Prerequisites

  • macOS or Linux
  • Lima (installed automatically via Homebrew if available)
  • A subscription or API key for your agent of choice

Install

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 bash

Option 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 bash

Then reload your shell (source ~/.zshrc) or open a new terminal.

Usage

One-time setup

devm setup

Creates 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 workloads

Define a VM

devm 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:rw

This saves a named VM definition to ~/.devmconfig. You can mount multiple directories, specify resource limits, forward ports, and pass environment variables.

Open a shell in the VM

devm shell myapp

Creates 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:

  • claude runs with --dangerously-skip-permissions
  • codex runs with --full-auto

Run commands

devm run myapp npm install
devm run myapp claude -p "fix all lint errors"
devm run myapp docker compose up -d

VM lifecycle

devm 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 VMs

To 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 VM

To resize an existing VM, pass resource flags — the config is updated and applied:

devm --cpus 8 --memory 16 shell myapp

Running 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 shell

Offline mode

devm --offline shell myapp         # Block outbound internet access

Blocks 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.

Update tools in a running VM

devm provision myapp               # Re-run the provisioning script

Updates Claude Code, OpenCode, Codex CLI, and mise inside an existing VM without rebuilding it.

Configuration

~/.devmconfig

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/docs

Settings:

  • cpus=N — Number of CPUs
  • memory=N — Memory in GiB
  • disk=N — Disk in GiB
  • ports=P1,P2,... — Ports to forward (supports ranges: 7200-7206)

Environment variables (env.*):

  • env.VAR — Forward from host environment
  • env.VAR=value — Hardcoded value
  • env.VAR= — Empty string

Mounts (mount=):

  • mount=/path — Smart default (git repos writable, others read-only)
  • mount=/path:rw — Force writable
  • mount=/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.

Per-user setup: ~/.devm/setup.sh

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 numpy

Per-user runtime: ~/.devm/runtime.sh

Create this file to run commands inside every VM on each start:

# ~/.devm/runtime.sh
export MY_API_KEY="..."

Per-directory: .devm.runtime.sh

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 -d

MCP servers

The 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.

How it works

  1. devm setup creates 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 template
  2. devm 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
  3. The VM persists after exit. Running devm shell or devm run with the same name reuses the same VM
  4. Use devm stop to stop or devm rm to delete. Use --rm to auto-delete after exit

What's in the VM by default

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

Security model

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.

Security features

  • 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: .git directories 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

Why not Docker?

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.

Credits

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

License

MIT

About

Sandbox your code to protect against dependency attacks, and let your AI agent work on its own

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages