Repository navigation
v0.6.0
The containment work accumulated since 0.5.6, and the first features that let a contained tool reach named things outside its box. It also carries everything from 0.5.7, which never shipped.
Before you upgrade (Windows)
If you ever ran nvx setup, run it again. Each project now gets its own sandbox identity, so the grants an earlier setup made name an identity nothing launches under. The symptom is npx failing with EPERM: operation not permitted, lstat 'C:\Users'. npm install, npm run and node are unaffected. nvx doctor reports a stranded setup and exits non-zero.
Removed
The wsl, wslc and systemd-nspawn providers, and NVX_EXPERIMENTAL with them. All three silently ignored network.mode: offline, and nspawn needed root and left root-owned files in your project. native and docker remain, along with sandbox-exec on macOS. Naming an unsupported provider stops the run, and the refusal now lists the providers that exist.
Reaching things outside the sandbox
--connect— reach one service already running on your machine: a browser with remote debugging on, a local database, an emulator.nvx --connect 9222:19222 npx some-tool. The contained side chooses when to connect, and nvx chooses where. Works on Windows, macOS and Linux; the docker provider says it cannot carry it rather than accepting the flag and doing nothing.network.mode: loopback— every service on your 127.0.0.1, without naming any. On macOS and Linux a raw connection arrives at the address it dialled, so a local database works; on Windows it covers what a proxy-aware client sends. Selecting it in a project policy needs approval, which it did not before: it was ranked as stricter than the default, so one line in a pull request could hand a contained install every local service.isolation.filesystem.allow_read_exec— let a contained tool read and run a program kept outside the project, such as Playwright's browsers. Read and execute only. The grant is recorded, and withdrawn when the policy stops asking for it.isolation.environment.allow— keep a named environment variable inside the sandbox. A name matchingAWS_,GITHUB_orSECRET_is refused: a policy file lives in your repository, so one line in one would otherwise hand a cloud credential to whatever an install script runs.
Applying the install checks selectively
Every check applies to every package until a policy names an exception, and each list waives only its own check.
{
"typosquatting": { "trusted_packages": ["my-internal-helper"] },
"release_age": { "trusted_packages": ["chrome-devtools-mcp", "@upstash/*"] },
"install_scripts": { "trusted_packages": ["esbuild", "sharp"] },
"vulnerabilities": { "allowed_advisories": ["GHSA-xxxx-yyyy-zzzz"], "min_severity": "high" }
}release_age.trusted_packages is the answer to an MCP server that will not start because its package was published this morning — a prompt is a denial when nothing can answer it. install_scripts.trusted_packages also waives enforce_ignore_scripts, which is how "block install scripts except for these" is written; every run that uses one names the package it let through. Advisories are accepted by ID rather than by package, so a finding published after your assessment still stops the install, and an advisory nvx could not rate stops it at every severity floor.
Adding any of these counts as loosening, so a project file naming a package needs approval.
Fixed
Security and containment:
- One nvx sandbox could reach another's loopback services, defeating the egress allowlist.
npm -- install evilran uncontained and skipped every pre-install check.- A runtime archive could write outside its destination through a chain of links.
- A project policy could switch on
isolated_toolswith no approval. - macOS: the Seatbelt profile was written where a contained process could reach it.
- Linux: a contained process could remove directories from the nvx runtime store.
Things that did not work:
- Bun could not run inside the Linux sandbox at all, and the Docker provider could not write to your project there.
- A contained process could not find the other runtime, so a postinstall calling
bunfrom Node, or the reverse, failed. - Inside the sandbox on macOS and Linux, a nested
nodelookup did not get the runtime you pinned. macOS ran the wrong version silently — measured as a pinned v22.23.2 alongside a nested v24.20.0 — and Linux failed the install outright. - Windows: the ninth piped child in a contained process hung. Linux: the sandbox refused to start on kernels between 5.13 and 6.9.
- Upgrading nvx while anything was running from it kept the old binary and reported success, so the version simply did not change.
Windows PATH, which had two separate defects:
- The installer flattened
%VAR%entries in your PATH, so anything written as%USERPROFILE%\binstopped resolving. The installer's PATH write is now covered by a test in CI. nvx doctor --fixchanged the type of your User PATH fromREG_EXPAND_SZtoREG_SZ, with the same effect.
Being told what happened:
- Containment stripped most of your environment without saying so, so a tool reading
CIstarted prompting and a build readingNODE_ENVquietly emitted a development bundle. It now names what it drops. nvx install tscwas refused as a typosquat ofms.- Three refusals reached an MCP client as a silently closed pipe.
nvx importdownloaded runtimes without asking;nvx import <unknown-source>exited 0.
That is a selection. See CHANGELOG.md for the rest, including the corrections made to documents that overstated what nvx contains.
Known issue
Bun needs 1.4.x inside the Windows sandbox. Older Bun keeps a working-directory descriptor captured at startup that an AppContainer will not honour, so 1.3.1 fails every relative-path operation with EBADFD. Bun added AppContainer support in 1.4.0. Node holds no such descriptor and is unaffected.
Install
Download the binary for your platform, or on Windows use install.ps1. Checksums are in SHASUMS256.txt.