Contents

vm2 Node.js Sandbox Escape 12 Critical Vulnerabilities Two Without a Patch

 

Want to learn ethical hacking? I built a complete course. Have a look!
Learn penetration testing, web exploitation, network security, and the hacker mindset:
→ Master ethical hacking hands-on
Hacking is not a hobby but a way of life!

 
Contents

Twelve critical vulnerabilities were just published for vm2, a Node.js security library that sits inside millions of applications. Three of them score a perfect 10 out of 10. The creator shut the project down in 2023 because it was too broken to fix, restarted it anyway in October 2025, and here we are.

The library is called vm2. When a platform lets users run their own code, that code needs somewhere to run where it cannot touch anything it should not touch. Not the files on the server, not the ability to run system commands, not connections to other services. That sealed-off space where code runs but cannot escape is called a sandbox. vm2 was the tool Node.js developers used to build one.

Node.js is the platform that lets JavaScript run on servers, not just in browsers. It runs the server side of a huge chunk of the internet. vm2 has appeared in the dependency trees of platforms including AWS Amplify, Salesforce, Replit, builder.io, and Dataform. Online coding platforms, chatbots, automation tools, any service that lets users submit their own scripts. The library pulls in more than 1.3 million downloads every week and around 900 other packages depend on it directly.

The way vm2 built that sealed space was with a JavaScript feature called Proxies. A Proxy works like a checkpoint between code and whatever it tries to touch. Every time code inside the sandbox reaches for something, the Proxy steps in and decides whether that is allowed. The sandbox and the rest of the application ran inside the same program, and those Proxies were what kept them from ever touching.

JavaScript makes that nearly impossible to hold together. It is a dynamic language with dozens of ways to get from one object to another that nobody fully mapped when vm2 was built. Prototype chains are how JavaScript objects pass down properties to each other, like a family tree. Walk that tree far enough in the wrong direction and code can reach parts of the system it was never supposed to touch. Async operations, the ones that handle things that take time like reading a file or waiting on data, hand back objects that the Proxies sometimes miss entirely. And Node.js itself grabs certain calls before vm2 can wrap them, so the checkpoint never gets to do its job.

One of vm2’s own contributors wrote it down in the project documentation: the security layer was built on top of something that actively worked against it.

Security researchers started testing those weak spots systematically in 2022. Among them was SeungHyun Lee of KAIST Hacking Lab in South Korea, who found multiple escapes in quick succession. A sandbox escape is what happens when code running inside that sealed space finds a crack, slips through, and runs directly on the server with full access to everything the surrounding application can reach. Eight security advisories in a single year, seven of them critical. Seven of those landed within a four-month window in 2023. Each one got patched. A different crack showed up each time.

In July 2023 the project stopped. The README said it clearly: critical security issues, do not use in production, we are done maintaining this.

The downloads barely moved. Around a million a week, even after that notice went up. There was no clean replacement that fit into codebases that had been running vm2 for years, so developers kept using it.

The project came back in October 2025 with version 3.10.0. All known problems fixed, and still working all the way back to Node.js version 6. Keeping that old compatibility was also what held the library back. The newer approaches to code isolation that would have made vm2 safer simply do not exist for those older versions of Node.js. Supporting old environments meant staying inside old limits.

Three months later, a new CVSS 9.8 was disclosed. CVSS is the Common Vulnerability Scoring System, the 0 to 10 scale the security industry uses to rate how serious a vulnerability is. A 9.8 means anyone on the internet can exploit it, no password needed, no click needed from anyone on the other side, full control of the system as the result.

This week, the maintainer published 12 more advisories himself on GitHub. All of them. He found or coordinated every single one, then patched them across several version updates. The person who built this library, shut it down because it could not be made safe, brought it back, and is now the one writing up how it keeps breaking.

Each escape goes through a different part of JavaScript. One uses __lookupGetter__, an old method from JavaScript’s early days that reads a special accessor function stored on an object. The problem is that vm2 has two versions of this method, one for inside the sandbox and one from the real system. When the method gets passed between those two worlds, vm2 hands over the real system version instead of its own. From there, code inside the sandbox can use it to climb up through JavaScript’s object tree, grab the real Function constructor from the system side, and build a new function that runs commands directly on the server. The whole escape fits in less than 20 lines.

One CVE is specifically a bypass of a fix from 2023. CVE-2026-24120 breaks through the patch that was applied for CVE-2023-37466, which KAIST researcher SeungHyun Lee found three years ago. The fix left a gap. Someone found it.

One goes through the species property on Promise objects. A Promise is how JavaScript handles things that take time. When the waiting is done, the .then() or .catch() callback runs with the result. vm2 checks and cleans the Promises it creates itself, but async functions hand back a Promise that comes straight from the real system and never goes through that cleaning step. One gap is all it takes.

This type of Promise escape is not new. In January 2026, CVE-2026-22709 used the exact same gap. The official GitHub advisory for that CVE includes the actual proof-of-concept code. It runs inside a vm2 sandbox and walks straight out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const { VM } = require("vm2");
const code = `
const error = new Error();
error.name = Symbol();
const f = async () => error.stack;
const promise = f();
promise.catch(e => {
    const Error = e.constructor;
    const Function = Error.constructor;
    const f = new Function(
        "process.mainModule.require('child_process').execSync('echo HELLO WORLD!', { stdio: 'inherit' })"
    );
    f();
});
`;
new VM().run(code);

That last line is the sandbox. Everything before it runs inside it, escapes it, and runs a command on the real server. The maintainer published this himself.

One escape works through SuppressedError, a newer error type in JavaScript that vm2 was not watching for. One combines a specific type of error, a TypeError that happens when converting a Symbol to a string, with WebAssembly exception handling. WebAssembly is a lower-level code format that runs inside V8, which is the JavaScript engine inside Node.js. V8 deals with WebAssembly errors before JavaScript ever gets to see them. vm2 sets up its defences at the JavaScript level. This escape happens a floor below that, where vm2 cannot reach.

Then there is CVE-2026-44007, which works differently from the others. It targets any application that uses the nesting: true setting in vm2. That setting is meant to allow sandboxes inside sandboxes. But with it turned on, code running inside the sandbox can load vm2 itself, spin up a brand new sandbox with no restrictions at all, and run whatever it wants. Even if the outer sandbox is configured with require: false, which is supposed to block loading modules. The inner sandbox ignores what the outer one says. One setting quietly switches off all the others.

CVE-2026-44005 has the widest reach of all of them. Most escapes give an attacker access to the server process. This one lets an attacker change Object.prototype and Function.prototype across the entire running application. Object.prototype and Function.prototype are the base blueprints that every object and every function in JavaScript is built from. If you change those, everything running in that same process is now working with poisoned building blocks, not just the sandbox, but the whole application around it.

CVECVSSDescriptionStatus
CVE-2026-241189.8Escape via __lookupGetter__patched in 3.11.0
CVE-2026-241209.8Bypass of the 2023 fix for CVE-2023-37466, via Promise speciespatched in 3.10.5
CVE-2026-247819.8Escape via inspect functionpatched in 3.11.0
CVE-2026-263329.8Escape via SuppressedErrorpatched in 3.11.0
CVE-2026-269569.8Symbol-to-string TypeError with WebAssembly exception handlingpatched in 3.10.5
CVE-2026-4399710.0Code injection, attacker retrieves host Objectpatched in 3.11.0
CVE-2026-439999.9Allowlist bypass, child_process loaded for direct command execpatched in 3.11.0
CVE-2026-4400510.0Prototype pollution of Object.prototype and Function.prototypepatched in 3.11.0
CVE-2026-4400610.0Code injection via BaseHandler.getPrototypeOfpatched in 3.11.0
CVE-2026-440079.1nesting:true bypasses require:false entirelypatched in 3.11.1
CVE-2026-440089.8Escape via neutralizeArraySpeciesBatch()NO PATCH AVAILABLE
CVE-2026-440099.8Escape via null proto exceptionNO PATCH AVAILABLE

Two of those twelve have no fix at all. CVE-2026-44008 and CVE-2026-44009 were published on May 3 with no patch, and as of today the official GitHub advisory still says “Patched versions: None” for both. Both were found by XmiliaH, the same contributor who documented the architectural problem in vm2’s own project files. Updating to 3.11.2 does not help with these two.

There is also a category of crash attacks that has no fix. Sandbox code can create an async function that throws an error in a specific way during the moment JavaScript builds the error message and stack trace. V8 handles that error using the system’s own Promise object before vm2 can get to it. On Node.js 15 and later, when that kind of rejection goes unhandled, it shuts down the entire process. The maintainer wrote in the npm README that fixing this would require changing how Node.js itself behaves, which is not something vm2 can do. The advice is to add your own handler that catches and swallows those rejections before they kill the process.

The current npm README also says something most coverage skips over: developers should not use vm2 for code coming from completely unknown outside sources, even in the fully patched version. That warning is right there on the install page that developers land on when they run npm install vm2.

The recommended replacement is isolated-vm. Where vm2 runs the sandbox inside the same process using Proxies, isolated-vm uses a feature built into V8 called an Isolate, which creates a completely separate JavaScript environment at the engine level. Not a fence around code in the same room, but a different room entirely. Right now isolated-vm gets 397,000 downloads a week. vm2 gets more than 1.3 million. The broken library is still more than three times more popular than the safer one.

The maintainer has said new escapes will keep appearing. Endor Labs counted more than 20 sandbox breakouts across vm2’s full history before this week. Add the current 12 and the total is past 30. The npm page of vm2 warns you not to trust it for untrusted code. The creator says more holes are coming. And it still gets more than 1.3 million downloads a week.

In cybersecurity, attribution is one of the hardest problems. These vulnerabilities came from independent researchers and from the maintainer himself. The technical picture is clear. Who is actively using these against real systems right now is harder to say. None of the current batch appear in CISA’s Known Exploited Vulnerabilities catalog yet, but the exploit code is public and the affected versions are everywhere.

Check if vm2 is in your project

Run this from the root of any Node.js project:

1
npm list vm2

If there is output, vm2 is in the dependency tree. It may be a direct dependency or pulled in by another package. Running npm audit from the same directory will also flag it with severity level and a direct link to the advisory.

What to do

Update to version 3.11.2, the current release:

1
npm update vm2

This patches ten of the twelve vulnerabilities. CVE-2026-44008 and CVE-2026-44009 have no patch yet. If the project runs code submitted by users at runtime, vm2 should not be trusted as the only protection regardless of which version is running. For those two unpatched CVEs specifically, the only real answer is to stop using vm2 for untrusted code and move to isolated-vm or a container-based approach.

The techniques behind all of this, exploitation, post-exploitation, privilege escalation, and moving from a foothold to full system access, are exactly what my ethical hacking course covers step by step:

Join my complete ethical hacking course

Hacking is not a hobby but a way of life. 🎯

Sources: vm2 Security Advisories · CVE-2026-22709 GitHub Advisory

 

→ Stay updated!

Get the latest posts in your inbox every week. Ethical hacking, security news, tutorials, and everything that catches my attention. If that sounds useful, drop your email below.

By Bulls Eye

Jolanda de koff • emaildonate

My name is Jolanda de Koff and on the internet, I'm also known as Bulls Eye. Ethical Hacker, Penetration tester, Researcher, Programmer, Self Learner, and forever n00b. Not necessarily in that order. Like to make my own hacking tools and I sometimes share them with you. "You can create art & beauty with a computer and Hacking is not a hobby but a way of life ...

I ♥ open-source and Linux