vm2, NodeVM and Ollama show why JavaScript runtime isolation keeps failing at 300,000 deployments
Recent vm2, NodeVM and Ollama flaws show a recurring failure pattern: developer-friendly JavaScript isolation is being treated as a hard security boundary when the runtime was never designed to provide one.
vm2, NodeVM and Ollama are different projects with the same uncomfortable lesson: developer-friendly runtime isolation is being asked to do the work of a hard security boundary. In vm2, CVE-2026-44005 exposed mutable host prototypes through proxies and carries a published CVSS score of 10.0. In NodeVM, CVE-2026-43999 let the module builtin defeat a builtin allowlist and reach host module loading, with a published CVSS score of 9.9. In Ollama, CVE-2026-7482 reportedly allowed unauthenticated remote memory disclosure from a process that may contain prompts, secrets and model data across more than 300,000 deployments, with a published CVSS score of 9.1.
These are not the same bug. That is the point. One is a JavaScript object model failure. One is a module loading failure. One is a memory disclosure flaw in AI infrastructure rather than a JavaScript sandbox escape. They still rhyme because the deployment pattern is the same: complex runtimes are being placed directly in front of untrusted input, then trusted to preserve boundaries they were not built to preserve under adversarial pressure.
The phrase "sandbox escape" makes the problem sound local and specific. A sandbox exists, an attacker escapes it, a patch closes the hole. That framing is too generous. What these cases show is closer to sandbox escape as a service: a recurring business model in which applications accept user-controlled code, prompts, plugins or requests, pass them into a powerful runtime and rely on library-level mediation to prevent the runtime from remembering that it is still connected to a host.
The vm2 failure was about shared JavaScript reality
vm2 exists because executing untrusted JavaScript is a common requirement and a bad idea. Plugin systems need it. Education platforms need it. Automation products need it. Internal developer tools, workflow engines and SaaS customisation layers keep rediscovering it. The promise is appealing: run user code inside a JavaScript sandbox, expose only selected objects and keep the host process safe.
CVE-2026-44005 has a published CVSS score of 10.0 and undermines that promise at the level of JavaScript's own object model. The advisory describes a sandbox escape caused by mutable proxies for host intrinsic prototypes. In practice, that means sandboxed code could reach and modify prototypes such as Object.prototype, Array.prototype and Function.prototype through proxy handling that did not preserve the intended boundary. Once host intrinsics become mutable from inside the sandbox, the distinction between guest and host code becomes a negotiation with the language runtime rather than a security invariant.
Prototype mutation is not a decorative feature in JavaScript. It is part of how the language resolves properties and behaviour. If untrusted code can influence the prototypes that trusted code later uses, the sandbox is no longer containing behaviour. It is exporting it. A protected boundary that shares mutable foundations with the thing it is protecting is a boundary in the same sense that a curtain is a wall.
This is why JavaScript sandboxing is so brittle. The runtime is dynamic by design. Objects can be wrapped, trapped, reflected, passed across membranes and modified at distances that are not obvious from the call site. vm2 and similar libraries try to build a membrane between worlds, but the membrane must correctly account for a language full of reflective operations, inherited behaviour, implicit coercion and host-provided objects. Every new escape tends to look narrow in isolation. Collectively, they show how hard it is to make a dynamic language forget its own affordances.
The security question is not whether vm2 had one defective handler. It is whether a handler-based mediation layer can be treated as equivalent to process isolation when the protected asset is the process itself.
NodeVM turned allowlisting into ambient authority
CVE-2026-43999 in NodeVM is a cleaner example because the failure mode is more familiar: an allowlist let through something that was more powerful than it looked. The issue affected NodeVM versions before 9.0.1, has a published CVSS score of 9.9 and centred on the module builtin. Exposing module allowed access to Node's module loading machinery, including Module._load(), which could then be used to reach modules outside the intended policy.
That matters because Node's builtin modules are not just libraries. They are authority. fs means filesystem access. child_process means process creation. net and http mean network access. In a normal application this is useful. In a sandbox it is the difference between evaluating a function and handing the guest a shell-shaped key.
Allowlists work only when the units being allowed are small, stable and understood. Node's module system is not that. It was designed to make host capabilities available to programmes, not to act as a capability-secure object graph for mutually suspicious tenants. The moment a supposedly safe builtin exposes a path back to the general loader, the allowlist stops being a boundary and becomes a routing table.
The lesson is not that module should have been removed from the allowlist, although that was the immediate fix. The deeper problem is that NodeVM was being used in threat models where one mistaken builtin could convert restricted code execution into host code execution. That is not defence in depth. It is a single configuration decision standing between a tenant and the host process.
This pattern appears repeatedly in application sandboxes. A product starts with a restrictive policy. Users need more functionality. A builtin is exposed. Then another. Then a compatibility flag appears because real scripts expect the normal runtime. Over time the sandbox becomes less a contained environment and more a slightly opinionated version of the host. Attackers do not need every door open. They need one object that still remembers where the doors are.
Ollama shows the same boundary problem in AI infrastructure
Ollama is not vm2. CVE-2026-7482 is described as an out-of-bounds read rather than a JavaScript escape. Its inclusion here is not about implementation similarity. It is about operational similarity.
Ollama became popular because it made local model execution simple. Developers could run large language models on their own machines or servers without sending prompts to a cloud provider. That simplicity changed its security role. A local inference tool became a network-facing service in development environments, internal platforms and some production systems. According to the reported disclosure, more than 300,000 Ollama deployments were exposed, and the vulnerability allowed unauthenticated remote attackers to read arbitrary process memory. The published CVSS score is 9.1.
The contents of that memory matter. An inference process can hold prompts, embeddings, request data, API tokens, model metadata and fragments of application state. For organisations using local models to keep data private, memory disclosure is a particularly direct failure. The data did not leave through a vendor API. It left through the local service that was meant to make external disclosure unnecessary.
The similarity to vm2 and NodeVM lies in the misplaced trust boundary. AI tooling often enters an organisation as developer infrastructure first and production infrastructure later. It is installed quickly, bound to a convenient interface, wired into automation and treated as internal because it began on a workstation. Then the service becomes reachable from other systems. At that point, unauthenticated parsing bugs and memory safety issues stop being local defects. They become remote data exposure.
This is the same path that JavaScript sandboxes followed. A developer convenience becomes an execution service. The execution service becomes multi-user. Multi-user becomes multi-tenant. Then the security model is discovered after the integration work is already finished.
The false economy of in-process isolation
In-process isolation is attractive because it is cheap. There is no container scheduler to maintain, no VM lifecycle to manage, no cold start budget to justify and no separate kernel boundary to design around. A library sandbox can be imported, configured and shipped. That is exactly why it keeps appearing in places where it should not be the primary control.
The cost is that the attacker shares the most valuable thing in the system: the process. If untrusted code runs inside the same process as secrets, credentials, internal clients, filesystem handles or network reachability, the sandbox must be perfect. It must correctly mediate every object crossing the boundary, every exception path, every getter, every proxy trap, every reflective operation, every module hook and every host integration point. Perfection is a poor dependency in a security design.
OS-level isolation is not magic. Containers are misconfigured. VMs have escape bugs. Sandboxing technologies such as seccomp, AppArmor, namespaces, gVisor, Firecracker and dedicated worker pools all introduce operational work and their own failure modes. The difference is that they move the primary boundary below the application runtime. A JavaScript proxy bug should not decide whether a tenant can read /etc/passwd. A module loader mistake should not decide whether an attacker can spawn a process on the host. A malformed inference request should not decide whether secrets from adjacent requests are recoverable from memory.
The right model is layered. Treat the language sandbox as a convenience and policy mechanism, not as the containment root. Put untrusted execution in a separate process at minimum. Run it as a low-privilege user. Remove ambient credentials. Apply filesystem and network restrictions outside the runtime. Use containers or microVMs when tenant separation matters. Destroy workers after use where feasible. Limit memory, CPU and wall-clock time. Assume the guest code will eventually find a runtime-level escape and ask what remains between that code and the organisation's assets.
That last question is the one many sandbox designs fail. If the answer is "the same Node.js process", the design is gambling on a very large object graph behaving exactly as intended forever.
AI agents make the old problem easier to reach
The next pressure point is not just untrusted JavaScript pasted into a web form. It is AI-assisted systems that generate, transform and execute code as part of normal operation. Agents write snippets. Workflow tools evaluate expressions. Chat interfaces call plugins. Data analysis notebooks execute generated code. Internal copilots increasingly sit near source repositories, ticketing systems, deployment tools and cloud credentials.
This creates a larger intake surface for sandboxed execution. The attacker may not need direct access to an admin console that accepts code. A prompt injection in a document, issue or web page may influence an agent into generating code that reaches the execution layer. Once that code is inside a runtime sandbox, old escape classes become part of a newer attack chain.
The industry tends to discuss prompt injection and sandbox escape as separate categories. Operationally, they converge. Prompt injection supplies the instruction. The agent supplies the code. The runtime supplies the execution context. The sandbox bug supplies the host. None of the pieces needs to be novel for the chain to work.
Ollama-style local inference makes this more complicated because it encourages organisations to pull model execution closer to sensitive systems. That can be the right privacy decision, but it changes the defensive burden. Local does not mean isolated. Internal does not mean trusted. A model server reachable from automation has to be threat-modelled like any other service that parses attacker-influenced input and holds sensitive data in memory.
What defenders should change
The practical advice is blunt: stop treating JavaScript runtime sandboxes as hard security boundaries. If an application accepts untrusted code, expressions, plugins or generated scripts, design the system as if the runtime sandbox will fail.
For vm2 and NodeVM specifically, inventory where they are used and why. Many organisations do not know which product feature depends on sandboxed JavaScript until a CVE lands. Search codebases and lockfiles for vm2, NodeVM and transitive packages that expose them. Identify whether the sandbox handles user-controlled input, tenant-controlled customisation or AI-generated code. The risk is materially different when sandboxed code comes from an administrator writing internal automation than when it comes from an external tenant.
Remove powerful builtins by default, but do not mistake that for containment. Restrict child_process, filesystem access, network modules and module loading paths. Freeze or harden intrinsics where the platform supports it. Log attempts to reach dangerous capabilities. These steps reduce easy abuse, but they do not remove the need for an outer boundary.
For execution services, isolate per tenant or per job where possible. Use short-lived workers. Mount only the files required for the task. Pass secrets through narrow, revocable channels rather than leaving broad credentials in the environment. Deny outbound network access unless the task explicitly requires it. Apply resource limits so denial of service is contained to the worker rather than the host.
For Ollama and similar AI services, bind interfaces deliberately rather than accepting defaults. Require authentication in front of any reachable endpoint. Segment model servers from systems that hold production secrets. Patch quickly when memory safety issues land. If a vulnerable instance was reachable by untrusted users, treat process memory as exposed for the affected period and rotate credentials that may have been resident in memory.
The common defensive move is to reduce ambient authority. A compromised sandbox should not inherit the host's permissions. A compromised model server should not have a clear path to source control, cloud metadata services or internal databases. A compromised worker should be replaceable, observable and boring.
The boundary has to be below the runtime
The recurring failure of vm2-style isolation is not a reason to abandon extensibility, plugins or local AI tooling. It is a reason to stop confusing developer ergonomics with containment. JavaScript sandboxes are useful for shaping an API. They are useful for reducing accidental damage. They are useful as one layer in a larger design. They are not, on their own, a trustworthy place to put hostile computation next to valuable secrets.
The same applies to AI infrastructure as it becomes another execution substrate. The model server, the agent runtime and the code sandbox are increasingly parts of one system. Each parses inputs that may be attacker influenced. Each holds state that may be sensitive. Each is tempting to expose because useful tools become more useful when other tools can call them.
Security boundaries fail most often where architecture pretends they are cheaper than they are. A real boundary has operational cost: processes, users, namespaces, policies, logs, patching, queues, latency and cleanup. The alternative is to keep paying for isolation with advisories after the fact, one escaped sandbox at a time.
Newsletter
One email a week. Security research, engineering deep-dives and AI security insights - written for practitioners. No noise.