Feb 4, 2026·6 min read·44 visits
Critical RCE in n8n versions < 2.4.8. The Python Code node's path validation logic fails when handling files that don't exist yet, allowing attackers to use symlinks to write files outside the sandbox. This leads to full host compromise.
A critical sandbox escape vulnerability in the n8n workflow automation platform allows authenticated users to execute arbitrary code on the host system. The flaw resides in the file path canonicalization logic of the Python Code node, specifically handling non-existent files. By exploiting a fallback mechanism that relies on string manipulation rather than filesystem validation, attackers can traverse directories via symlinks, effectively breaking out of the intended security boundaries. This turns a low-privilege workflow editor into a full system administrator.
Automation tools like n8n are the glue of the modern internet. They take data from point A, massage it, and shove it into point B. To make this truly powerful, n8n offers a Python Code node, effectively giving users a shell environment to run custom scripts. Ideally, this environment is a padded room—a sandbox where you can play with data but can't touch the sharp objects (the host filesystem).
However, building a perfect jail is notoriously difficult, especially when you allow users to interact with files. The security model relies on a simple promise: "You can play with files, but only inside this specific directory." It sounds simple enough. You check the path, make sure it starts with /sandbox/safe/, and let the script run. But filesystem paths are not simple strings; they are pointers to a physical reality that can be manipulated.
CVE-2026-25115 is the story of what happens when a developer trusts a string representation of a path more than the filesystem itself. It’s a classic tale of "it looked safe on paper," leading to a complete compromise of the host server. If you run n8n and allow untrusted users to create workflows, your server isn't yours anymore—it's theirs.
The vulnerability lies in a function called resolvePath in packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts. The purpose of this function is to take a user-provided path and return its canonical, absolute form. This is the gatekeeper. If the resolved path doesn't start with the allowed sandbox root, the operation is blocked.
Here is where the logic went sideways. The code correctly used fs.realpath() for files that existed. fs.realpath() is the gold standard because it asks the OS, "Hey, where does this inode actually live?" It resolves symlinks, .. segments, and returns the hard truth.
But what if the file doesn't exist yet? For example, if the script is trying to create a new output file? The original code caught the ENOENT (Error NO ENTry) exception and fell back to path.resolve(). This was the fatal mistake. path.resolve() is a library function that performs string manipulation. It doesn't look at the disk. It doesn't know that a folder in the path is actually a symlink pointing to /root. It just cleans up the string. The gatekeeper was checking a map drawn by the attacker, not the terrain itself.
Let's look at the specific lines that killed the security model. In the vulnerable version, the error handling explicitly downgraded the security check from a filesystem operation to a string operation.
// VULNERABLE CODE
async function resolvePath(path: PathLike): Promise<ResolvedFilePath> {
try {
// The safe way: ask the filesystem
return (await fsRealpath(path)) as ResolvedFilePath;
} catch (error: unknown) {
// If the file is missing (ENOENT)...
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
// THE BUG: Trusting the string representation
return resolve(path.toString()) as ResolvedFilePath;
}
throw error;
}
}When the patch landed in commit 5c69970acc7d37049deae67da861f92d2aaa9b03, the fix was obvious in hindsight. If the file doesn't exist, you can't resolve it, but you must resolve its parent directory. You cannot trust any part of the path that isn't anchored to the physical disk.
// PATCHED CODE
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
const dir = dirname(pathStr);
const file = basename(pathStr);
// The fix: Force resolution of the parent directory
const resolvedDir = await fsRealpath(dir);
return join(resolvedDir, file) as ResolvedFilePath;
}By forcing fsRealpath(dir), the application ensures that if dir involves a symlink escaping the sandbox, resolvedDir will reflect the true location (e.g., /etc/), causing the subsequent prefix check to fail.
To exploit this, we need to trick the application into thinking a file write is safe when it isn't. The strategy relies on the difference between the path string and the path reality. We will use the Python Code node to set up a trap.
Step 1: The Setup
First, we use valid Python code to create a symlink inside our allowed sandbox directory. The system allows this because we aren't leaving the sandbox yet.
os.symlink('/', '/sandbox/data/tunnel')
Now, /sandbox/data/tunnel points to the root of the filesystem.
Step 2: The Bait
We tell n8n we want to write a file to /sandbox/data/tunnel/tmp/pwned. The file doesn't exist, so fs.realpath throws ENOENT.
Step 3: The Switch
The vulnerable catch block executes path.resolve('/sandbox/data/tunnel/tmp/pwned'). Since path.resolve treats this as a string, it returns exactly what we gave it. The security check looks at the string: "Does it start with /sandbox/data? Yes." The gate opens.
Step 4: Execution
n8n performs the actual file write using the path /sandbox/data/tunnel/tmp/pwned. The operating system sees the symlink tunnel, resolves it to /, and writes the file to /tmp/pwned on the host. We have now achieved arbitrary file write. From here, overwriting .ssh/authorized_keys or a cron job for full RCE is trivial.
This is a CVSS 9.4 Critical vulnerability for a reason. n8n is often deployed with access to sensitive internal APIs, database credentials, and cloud keys. It sits at the center of an organization's automation infrastructure.
An attacker who exploits this doesn't just get to write a text file; they get the identity of the n8n process. In Docker environments, they might break out of the container next. In bare-metal installs, they own the server. They can steal all the credentials stored in n8n credential store, modify other workflows to exfiltrate data, or install persistent backdoors.
Because n8n is designed to execute code, the "Attack Complexity" is low. You don't need to bypass ASLR or craft heap sprays. You just need to write three lines of Python.
The remediation is straightforward: Update to version 2.4.8. The n8n team responded quickly and the patch effectively kills the symlink vector by verifying the parent directory of any new file.
If you cannot patch immediately, you are in a precarious position. The only effective mitigation is to disable the Python Code node entirely or restrict access to the workflow editor to only the most trusted personnel. Network segmentation won't save you here—the call is coming from inside the house.
For security researchers and developers, the lesson is clear: String manipulation is not security. If you are validating paths, you must ask the filesystem for the truth, the whole truth, and nothing but the truth.
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.io | < 2.4.8 | 2.4.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-693 |
| Attack Vector | Network (Authenticated) |
| CVSS Score | 9.4 (Critical) |
| Impact | Remote Code Execution (RCE) / Sandbox Escape |
| Patch Commit | 5c69970acc7d37049deae67da861f92d2aaa9b03 |
| Exploit Status | PoC Available (Theoretical) |
Protection Mechanism Failure
CVE-2026-48861 is a client-side HTTP request-line CRLF (Carriage Return Line Feed) injection vulnerability in the popular Elixir HTTP client library, Mint. The vulnerability permits HTTP Request Splitting and HTTP Request Smuggling when an application forwards untrusted, attacker-controlled inputs to Mint's HTTP client requests as either the HTTP request method or target. By embedding CRLF characters within these parameters, an attacker can terminate the request line prematurely, inject malicious headers, or pipeline entirely independent requests. These smuggled requests are then processed by upstream or downstream proxy servers as separate HTTP queries on the same TCP connection. While Mint version 1.7.0 introduced target validation to secure the request target, the HTTP request method parameter remained completely unvalidated. This flaw allows attackers to bypass routing filters, access restricted internal APIs, or poison HTTP caches under default configurations.
An Inconsistent Interpretation of HTTP Requests (HTTP Request/Response Smuggling) vulnerability in the Elixir Mint HTTP client allows attacker-controlled HTTP/1 servers to desynchronize response framing on shared connections due to over-lenient parsing of sign-prefixed Content-Length headers.
An allocation of resources without limits or throttling vulnerability in Elixir Mint allows an attacker-controlled HTTP/2 server to exhaust memory in a Mint client. The vulnerability is exploited by sending a HEADERS frame without the END_HEADERS flag followed by an infinite stream of CONTINUATION frames. Because the client lacks limits on the incoming header-block accumulator, the client continuously consumes memory until an out-of-memory crash occurs.
CVE-2026-48596 is an Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Request/Response Splitting, CWE-113) in the Elixir Tesla HTTP client. The flaw resides in how multipart content-type parameters are joined and serialized, enabling attackers to inject arbitrary headers or split HTTP requests when applications pass untrusted inputs to the parameters of multipart uploads.
An improper handling of highly compressed data (decompression bomb) vulnerability exists in the Elixir Tesla HTTP client when utilizing response decompression middlewares. By serving highly compressed responses or stacked content-encoding headers, a malicious server can cause arbitrary heap exhaustion, leading to a denial of service (DoS) crash in the BEAM virtual machine.
A high-severity security vulnerability in Elixir's Tesla HTTP client library (CVE-2026-48595) allows unauthenticated remote attackers to harvest sensitive credentials, including Authorization headers and cookies. The flaw resides in the 'Tesla.Middleware.FollowRedirects' component, which performs case-sensitive lookups when stripping credentials during cross-origin redirects. Because HTTP headers are case-insensitive by RFC specifications, standard canonical casing (e.g., 'Authorization') bypasses the lowercase-only blocklist, leaking tokens to untrusted external redirect destinations.