A look at how Plumber isolates pipeline steps on Windows, macOS, and Linux — from clean-env to Linux user namespaces and macOS Seatbelt — and why we chose each approach.
Abi O.
Creator
Running arbitrary shell commands from a pipeline locally raises an obvious question: what prevents a buggy or malicious sh step from writing to /etc or reading your SSH keys?
Plumber has four sandbox modes. Each uses the strongest isolation available on the platform without requiring any extra installation.
The baseline for every platform: shell steps inherit a minimal, clean environment instead of your full shell session. Plumber passes through only the variables that tools actually need:
PATH, HOME, USER, LOGNAME, SHELL
LANG, LC_ALL, LC_CTYPE
TMPDIR, TEMP, TMP
GOPATH, GOROOT
NODE_PATH, NPM_CONFIG_CACHE
JAVA_HOME, MAVEN_HOME, GRADLE_HOME
SSH_AUTH_SOCK, SSH_AGENT_PID
GIT_SSH, GIT_SSH_COMMAND
Your AWS_SECRET_ACCESS_KEY, personal git tokens, and other secrets are never passed through. Plumber also always injects CI=true and PLUMBER=1 so your scripts can detect the environment.
On Linux, Plumber uses unshare to run each step in an isolated namespace:
unshare --user --pid --fork --map-root-user sh -c "your command"
What this provides:
unshare is part of util-linux, installed on every Linux distro. Zero extra dependencies.
macOS ships with sandbox-exec in every installation. Plumber wraps steps with a Seatbelt profile that uses a deny-on-system-paths / allow-everywhere-else approach:
/usr/lib, /Applications, etc.)/etc, /usr/bin, /System, /Applications are denied/tmp are explicitly allowedOn Windows with WSL installed, steps run inside the Linux environment, giving you the full Linux namespace stack for free.
Without WSL, Plumber falls back to a Win32 Job Object — a kernel mechanism that groups the process and all its children together so they can be tracked and terminated as a unit. If a step times out or the pipeline is cancelled, every child process is killed cleanly.
Debian and Ubuntu intentionally ship Python without ensurepip, which breaks python3 -m venv with a confusing error about the package not being available. Plumber detects this automatically at pipeline start and writes a minimal ensurepip shim into the user's local site-packages (~/.local/lib/pythonX.Y/site-packages/ensurepip/). The shim delegates pip installation to the system pip3 with no sudo or apt-get required. The check runs once per WSL session and is a no-op if ensurepip is already present.
On startup, Plumber probes for the strongest available mode and selects it automatically:
| Platform | Probe | Selected mode |
|---|---|---|
| Linux | unshare --user --fork --map-root-user true | User namespaces |
| Linux (restricted) | probe fails | Clean environment |
| macOS | which sandbox-exec | Seatbelt |
| Windows | wsl --status | WSL (+ Python bootstrap) |
| Windows (no WSL) | fallback | Job Object |
You can see and override the detected mode in Settings → Sandbox. If you're on a locked-down corporate machine where unshare is blocked, Plumber gracefully falls back to clean-env.
None of these modes require Docker, VMs, or root access on the host.