Repository navigation
v0.5.4
Upgrade if you use nvx on Linux. The sandbox there could not run anything at all, and three checks that should have caught it were reporting success without testing anything. Windows and macOS are unaffected by the fix; what changes for everyone is how much of the containment story is now backed by a machine rather than by a design document.
The Linux sandbox could not start the program it was supposed to contain
Every contained command on Linux failed with no such file or directory, naming a runtime that was present and executable. Not an edge case — nothing ran.
The sandbox was putting your program into a second, nested user namespace of its own. Setting one of those up means writing a couple of files under /proc, and by that point the sandbox has already locked the filesystem down, and /proc is not on its allowed list. So the kernel refused, and the program never started.
It reported a missing file rather than a refusal because of where the supervisor sits: it has its own process-ID namespace while still seeing the host's /proc, so the path it builds names nothing there and fails to open before permissions are ever consulted. That one detail is why this read as a missing runtime for as long as it did.
The nested namespace is gone. Your program keeps its own filesystem namespace and takes the user namespace from the supervisor, which is where it belongs. Nothing is less contained — the second namespace only handed the program a fresh one to be root in.
Three Linux checks were green while testing nothing
This is why the above survived. Each check asked "can I create a network namespace?" in a way only the root user can answer yes to, so all three skipped themselves on every ordinary machine, including the one in CI. The containment check went further and explained away its own silence: finding no result, it blamed the Linux distribution for restricting namespaces, without ever checking whether it had. It hadn't.
Once they actually ran, both smoke checks turned out never to switch the sandbox on. The one asserting that a contained program cannot write outside its project could not have passed, and the network one was measuring whether the machine had working DNS rather than whether the allowlist worked.
All of them now test what they claim to, and the containment check fails — rather than shrugging — when the sandbox had everything it needed and still produced nothing.
Containment is now checked on real Linux and macOS machines
Every build runs a probe on a hosted runner of each operating system, and each probe asserts both what must be blocked and what must still be allowed. That second half matters: a sandbox that refuses everything is a broken launch, not a secure one, and only the positive checks tell them apart.
| Linux | macOS | |
|---|---|---|
| Write outside the project | blocked | blocked |
| Write inside the project | allowed | allowed |
| Read outside the project | blocked | allowed |
| Reaching a host that is not allowlisted | blocked | blocked |
| Reaching a host that is allowlisted | works | not tested |
Windows containment continues to be verified by replaying the original attacks locally, because hosted Windows runners cannot start the sandbox at all.
Honesty note
macOS still does not contain filesystem reads. A contained install can read ~/.ssh, ~/.aws and ~/.npmrc. This is deliberate — the dynamic linker needs system libraries whose locations vary by macOS version, and a strict read allowlist stops programs launching — and it is now asserted on purpose: the probe requires that read to succeed, so if anyone ever tightens the profile, the build fails and the documentation has to be updated in the same change.
What v0.5.3's notes said about macOS being unverified is no longer true, and was already stale when written. A macOS runner now confirms write containment and egress denial. Four things there are still unchecked and are not claimed: that an allowlisted host gets through the proxy, that UDP specifically is refused, that nvx fails safely if sandbox-exec is missing, and which layer refuses the outbound connection it does refuse.
docs/enforcement-matrix.md states, cell by cell, what is measured, what is checked in CI, and what rests only on the generated policy.