highCVE-2026-86076Sep 10, 2026

CVE-2026-86076: n8n Expression Sandbox Escape via Class-Field Sanitizer Rebinding

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A flaw in n8n's expression compiler lets an attacker overwrite the internal sanitizer function using a class field, bypassing all property access checks and reaching the JavaScript Function…

Packagen8n
Ecosystemnpm
Affected< 1.123.76
Fixed in1.123.76
CVE-2026-86076: n8n Expression Sandbox Escape via Class-Field Sanitizer Rebinding

The problem

n8n's expression engine wraps every member access in a call to this.__sanitize(expr), where this is the __data runtime object. Before the fix, __data.__sanitize was a plain, writable property.

Any expression author could define a class with a field also named __sanitize. Because the compiled expression runs in a scope where this resolves to __data, that field assignment silently overwrote the real sanitizer. With sanitization gone, forbidden names like constructor passed through unchecked, reaching the Function constructor for full code execution.

On the backend this means RCE as the n8n process user. In the editor preview, the same technique executes JavaScript in the session of any user who opens the workflow.

Proof of concept

A working proof-of-concept for CVE-2026-86076 in n8n, with the exact payload below.

javascript
{{ new class { __sanitize = x => x; get val() { return this.__sanitize('constructor'); } }.val.constructor('return process')().env }}

The PrototypeSanitizer AST hook rewrites every member access to obj[this.__sanitize(key)] and calls the compiled expression with .call(__data). Before the patch, __data.__sanitize was a plain writable data property, so a class-field initializer __sanitize = x => x inside the expression silently replaced it with an identity function.

With the real sanitizer swapped out, the key string 'constructor' was no longer blocked, letting the expression walk up to Function and execute arbitrary code (CWE-94).

The fix in PR #27178 replaced the plain assignment with Object.defineProperty using a throwing setter, so any attempt to overwrite __sanitize raises an ExpressionError instead of silently rebinding it. The patch also adds __sanitize itself to the set of rejected reserved class-member names in the AST pass.

The fix

Upgrade to n8n 1.123.76, 2.37.7, or 2.38.2. The fix (PR #27178) makes __data.__sanitize non-writable via Object.defineProperty with a throwing setter, and adds __sanitize to the block-list of reserved class-member names rejected at AST compile time. If upgrading is not immediately possible, restrict workflow-create and workflow-edit permissions to fully trusted users only, audit existing workflows for unexpected expressions, and set N8N_EXPRESSION_ENGINE=vm as a short-term workaround.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research