Sandboxing tasks
Prove a task reads only what it declares.
- Add
sandboxto the task’sexec.sandbox: {}allows nothing in the workspace, not even the package. Outside the workspace root (~/.cache,/etc) reads are open and fold into no key: declare what the output depends on as a key input. - Grant the package:
allow: { read: ['.'] }. Itsnode_modulesis readable already. Another package of yours, linked there, is readable when the task depends on one of that package’s command tasks; an uncached task reads every linked package. - Grant each output directory in
write, and each host innetwork. The sandbox does not readcache: declare both. - Run the task. An undeclared read or write fails it and names the path.
- Declare that path, or silence a noisy tool’s path with
ignore.
Config
Section titled “Config”import { defineProject } from '@vzn/vx'
export default defineProject({ tasks: { build: { exec: { command: 'vite build', sandbox: { allow: { read: ['.', '~/.cache/ms-playwright'], write: ['dist/**'], network: ['registry.npmjs.org'], }, deny: { network: ['telemetry.example.com'] }, ignore: { write: ['*.bun-build'] }, }, }, cache: { inputs: { files: ['src/**', 'index.html'] }, outputs: { files: ['dist/**'] } }, }, },})What you can grant
Section titled “What you can grant”allow takes every key below. deny takes only network. Domain lists are one union per run, which every sandboxed task reaches; deny.network is refused to every sandboxed task. ignore takes read, write, systemInfo and network, as patterns, and refuses the rest.
| Key | Grants |
|---|---|
read | paths or globs: package-relative, absolute or ~/ |
write | paths or globs; a directory ends in / or is a glob (dist/**) |
network | true, or a list of domains (*.sentry.io) |
localBinding | bind localhost ports; a list ([3000]) makes them reachable from outside |
unixSockets | true, or socket paths |
systemInfo | sysctl names a tool probes (vfs.disk-space) |
machLookup | macOS services (com.apple.FSEvents) |
pty | a terminal |
gitConfig | writes to .git/config |
The boundary is the project
Section titled “The boundary is the project”A task never reaches another package or a root file you did not grant, bar the linked packages of step 2. That wall is silent. An undeclared touch of the task’s own files fails the task, and a failed task is never cached.
Requirements & platform support
Section titled “Requirements & platform support”- Linux:
bubblewrap(bwrap),socatandripgrep(rg);straceto name an undeclared read.vx infosays if your host can. - macOS: the system sandbox. Its report can miss a record under load; the denial never does.
- Windows: under WSL.
What can’t be sandboxed
Section titled “What can’t be sandboxed”- A group task: it has no command.
- A task that itself sandboxes, on macOS (a sandbox cannot nest).
Common problems
Section titled “Common problems”write /proc/self/uid_map: Operation not permitted. You are root in a container. Run as a normal user, or setweakerWhenNested: true.File existsfrom the task’s ownmkdir. A write grant with no trailing slash is a file. Write'coverage/'.- On Linux a file made during the run is denied. A glob expands when the task starts: grant its directory.
read packages/ui through packages/app/node_modules/@x/ui, and its key folds no task of @x/ui. The task imports a sibling its key never sees. Depend on a command task of it (dependsOn: ['^source'], or^build), or grant and key the files yourself.