SkillAgentSearch skills...

Vibe

Easy Linux virtual machine on MacOS to sandbox LLM agents.

Install / Use

/learn @lynaghk/Vibe
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Vibe is a quick, zero-configuration way to spin up a Linux virtual machine on Mac to sandbox LLM agents:

$ cd my-project
$ vibe

░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
 ░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
 ░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓██████▓▒░
  ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
  ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
   ░▒▓██▓▒░  ░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░

Host                                      Guest                    Mode
----------------------------------------  -----------------------  ----------
/Users/dev/work/my-project                /root/my-project         read-write
/Users/dev/.cache/vibe/.guest-mise-cache  /root/.local/share/mise  read-write
/Users/dev/.m2                            /root/.m2                read-write
/Users/dev/.cargo/registry                /root/.cargo/registry    read-write
/Users/dev/.codex                         /root/.codex             read-write
/Users/dev/.claude                        /root/.claude            read-write
/Users/dev/.gemini                        /root/.gemini            read-write

root@vibe:~/my-project#

On my M1 MacBook Air it takes ~10 seconds to boot.

Dependencies:

  • An ARM-based Mac running MacOS 13 (Ventura) or higher.
  • A network connection is required on the first run to download and configure the Debian Linux base image.
  • That's it!

Why use Vibe?

  • LLM agents are more fun to use with --yolo, since they're not always interrupting you to approve their commands.
  • Sandboxing the agent in a VM lets it install/remove whatever tools its lil' transformer heart desires, without wrecking your actual machine.
  • You control what the agent (and thus the upstream LLM provider) can actually see, by controlling exactly what's shared into the VM sandbox. (This project was inspired by me running codex without --yolo and seeing it reading files outside of the directory I started it in --- not cool, bro.)

I'm using virtual machines rather than containers because:

  • Virtualization is more secure against malicious escapes than containers or the MacOS sandbox framework.
  • Containers on MacOS require spinning up a virtual machine anyway.

Finally, as a matter of taste and style:

  • The binary is < 1 MB.
  • I wrote the entire README myself, 100% with my human brain.
  • The entire implementation is ~1500 lines of Rust.
  • The only Rust dependencies are the Objc2 interop crates and the lexopt argument parser.
  • There are no emoji anywhere in this repository.

Install

Vibe is a single binary built with Rust.

Download the latest binary built by GitHub actions and put it somewhere on your $PATH:

curl -LO https://github.com/lynaghk/vibe/releases/download/latest/vibe-macos-arm64.zip
unzip vibe-macos-arm64.zip
mkdir -p ~/.local/bin
mv vibe ~/.local/bin
export PATH="$HOME/.local/bin:$PATH"

If you use mise-en-place:

mise use github:lynaghk/vibe@latest

I'm not making formal releases or keeping a change log. I recommend reading the commit history and pinning to a specific version.

You can also install via cargo:

cargo install --locked --git https://github.com/lynaghk/vibe.git

Using Vibe

vibe [OPTIONS] [disk-image.raw]

Options

  --help                                                    Print this help message.
  --version                                                 Print the version (commit SHA and build date).
  --no-default-mounts                                       Disable all default mounts, including .git and .vibe project subfolder masking.
  --mount host-path:guest-path[:read-only | :read-write]    Mount `host-path` inside VM at `guest-path`.
                                                            Defaults to read-write.
                                                            Errors if host-path does not exist.
  --network [nat | vznat | <bridge interface>]              Guest networking mode (default `nat`).
                                                            Providing an interface (e.g., `en0`) exposes the VM on that interface.
                                                            This is just like plugging it in, so it'll get its own IP address, be able to accept incoming connections, etc.

  --cpus <count>                                            Number of virtual CPUs (default 2).
  --ram <megabytes>                                         RAM size in megabytes (default 2048).
  --script <path/to/script.sh>                              Run script in VM.
  --send <some-command>                                     Type `some-command` followed by newline into the VM.
  --expect <string> [timeout-seconds]                       Wait for `string` to appear in console output before executing next `--script` or `--send`.
                                                            If `string` does not appear within timeout (default 30 seconds), shutdown VM with error.

Invoking vibe without a disk image:

  • shares the current directory with the VM
  • shares package manager cache directories with the VM, so that packages are not re-downloaded
  • shares the ~/.codex directory with the VM, so you can use OpenAI's codex
  • shares the ~/.claude directory with the VM, so you can use Anthropic's claude
  • shares the ~/.gemini directory with the VM, so you can use Google's gemini-cli

The first time you run vibe, a Debian Linux image is downloaded to ~/.cache/vibe/, configured with basic tools like gcc, mise-en-place, ripgrep, rust, etc., and saved as default.raw. (See provision.sh for details.)

Then when you run vibe in a project directory, it copies this default image to .vibe/instance.raw, boots it up, and attaches your terminal to this VM.

When you exit this shell, the VM is shutdown. The disk state persists until you delete it. There is no centralized registry of VMs --- if you want to delete a VM, just delete its disk image file.

Other notes

  • Apple's VZNATNetworkDeviceAttachment loses packets and VMs get wrecked whenever host networking changes (e.g., switching between wifi/ethernet/VPN) and VZBridgedNetworkDeviceAttachment requires kowtowing to acquire the restricted com.apple.vm.networking entitlement. To achieve reliable networking, Vibe bundles the vmnet-helper and automatically runs it for your VMs. On MacOS < 26, this requires some sudoers magic --- if necessary, Vibe will give you the appropriate incantations to run.

  • The default VM disk is 20 GiB, but only uses about 2.5 GiB. Since Apple Filesystem is copy-on-write and doesn't count zeros, disk space is only used when you actually write new blocks. You can use du -h to see how much space is actually consumed:

    $ ls -lah .vibe/instance.raw
    -rw-r--r--  1 dev  staff    20G Feb 11 21:57 .vibe/instance.raw
    
    $ du -h .vibe/instance.raw
    2.5G    .vibe/instance.raw
    

    If you need even more space within the VM, e.g., 100 GiB, run truncate -s 100G .vibe/instance.raw on your Mac and then within the VM run growpart /dev/vda 1 && resize2fs /dev/vda1.

  • MacOS only lets binaries signed with the com.apple.security.virtualization entitlement run virtual machines, so vibe checks itself on startup and, if necessary, signs itself using codesign. SeCuRiTy!

  • Debian "nocloud" is used as a base image because it boots directly to a root prompt. The other images use cloudinit, which I found much more complex:

    • Network requests are made during the boot process, and if you're offline they take several minutes to timeout before the login prompt is reached (thanks, systemd-networkd-wait-online.service).
    • Subsequent boots are much slower (at least, I couldn't easily figure out how to remove the associated cloud init machinery).
  • Claude Code requires both your ~/.claude folder (shared in the VM by default) and also the ~/.claude.json file for auth credentials and session history. VirtioFS only works with folders, so there's not a nice way to "mount" the latter inside the VM. Here's what I recommend:

    • Run claude and login. (You can do this in a VM or on your actual machine if you trust claude.)

    • mv ~/.claude.json ~/.claude/dot_claude_dot_json_should_have_been_here.json

    • make a shell alias/script to launch Vibe as:

      vibe --send "ln -fs ~/.claude/dot_claude_dot_json_should_have_been_here.json ~/.claude.json" \
           --send "IS_SANDBOX=1 claude --allow-dangerously-skip-permissions --dangerously-skip-permissions"
      

Alternatives

Here's what I tried before writing this solution:

  • Sandboxtron - My own little wrapper around Mac's sandbox-exec. Turns out both Claude Code and Codex rely on this as well, and MacOS doesn't allow creating a sandbox from within a sandbox. I considered writing my own sandboxing rules and running the agents --yolo, but didn't like the risk of configuration typos and/or Mac sandbox escapes (there are a lot --- I'm not an expert, but from this HN discussion I figured virtualization would be safer).

  • Lima, quick Linux VMs on Mac. I wanted to like this, ran into too many issues in first 30 minutes to trust it:

    • The
View on GitHub
GitHub Stars843
CategoryDevelopment
Updated2h ago
Forks45

Languages

Rust

Security Score

95/100

Audited on Apr 1, 2026

No findings