Git-R-Done: RCE in n8n via Config Injection
Feb 5, 2026·7 min read·6 visits
Executive Summary (TL;DR)
n8n interprets strings in double curly braces as code. By crafting a malicious `.git/config` file (e.g., in a repo cloned by n8n), an attacker can inject JavaScript payloads. When the Git node lists the config, n8n evaluates the payload, leading to full RCE.
A critical Remote Code Execution (RCE) vulnerability in n8n's Git node allows authenticated users to execute arbitrary commands via malicious Git configuration values. This creates a classic sandbox escape scenario where data is treated as code.
The Hook: Low-Code, High-Risk
We love low-code platforms. They empower marketing teams to build complex automations without bothering the engineering department. But there's a dark side to this democratization of logic: the abstraction layer. Under the hood, platforms like n8n are essentially massive, always-on evaluation engines that take input from one place, treat it like a magical template, and shove it into another place.
In this episode of "Why We Can't Have Nice Things," we are looking at CVE-2026-25049, a critical RCE in n8n. The vulnerability lies in the Git Node. This component is designed to help you manage version control within your workflows—pulling repos, pushing changes, and listing configurations. It sounds innocent enough.
However, n8n has a feature—or a bug, depending on who you ask—called "Expression Evaluation." Anything wrapped in {{ }} is treated as JavaScript and executed. The problem arises when the platform trusts external data sources to not contain these magic delimiters. Spoiler alert: Git config files are just text files, and they definitely shouldn't be trusted.
The Flaw: A Tale of Two Evals
To understand this exploit, you have to understand how n8n processes data. When a node runs, it outputs JSON. When the next node runs, it can reference that JSON. If you drag-and-drop a value in the n8n UI, it creates an expression like {{ $node["Git"].json["remote.origin.url"] }}.
The vulnerability is a classic case of Improper Control of Dynamically-Managed Code Resources (CWE-913), specifically Expression Injection. The Git node's "List Config" operation reads the .git/config file, parses the INI format, and spits out a JSON object containing keys like remote.origin.url.
Here is the logic flaw: The Git node didn't sanitize the values it read from disk. It just passed them along as strings. If an attacker controls the git repository (or can inject into the config), they can set the URL to something like https://{{require('child_process').execSync('calc')}}@github.com.
When the Git node runs, it successfully reads that string. It's just text. But the moment a subsequent node references that value, the n8n expression engine wakes up. It sees the curly braces, assumes it's a template meant for it, and executes the JavaScript code inside. It's a delayed-trigger landmine.
The Code: The Smoking Gun
Let's look at the implementation. The vulnerability existed in packages/nodes-base/nodes/Git/GenericFunctions.ts. The code was blindly trusting the output of the git command.
The Vulnerable Logic
Before the patch, the code essentially did this (simplified):
// Pseudocode of the vulnerability
const config = await git.listConfig();
return config.all; // Returns raw strings containing {{...}}The Fix
In commit 78608969 (and follow-up 936c06cf), the developers realized that allowing raw strings from a config file to pass through to the expression engine was a bad idea. They introduced a sanitizeUrl function.
> [!NOTE]
> The fix relies on the JavaScript URL constructor. By parsing the string as a URL object and then stringifying it back, special characters like { and } are percent-encoded (e.g., %7B).
Here is the essence of the patch:
// The mitigation strategy
const sanitizeUrl = (url: string) => {
try {
const urlObj = new URL(url);
urlObj.password = ''; // Also strips creds, nice bonus
urlObj.username = '';
return urlObj.toString(); // Returns encoded string
} catch (error) {
return url;
}
};While this stops the remote.origin.url vector, it feels like a game of Whac-A-Mole. If the attacker finds a config value that isn't run through this sanitizer but is displayed by the node, the game is back on.
The Exploit: Escaping the Sandbox
Exploiting this requires a bit of setup, but it's devastatingly reliable. We need to trick n8n into processing a Git config file we control. This could happen if the workflow clones a public repository we own, or if we have partial access to the system.
The Attack Chain
- Preparation: Create a malicious git repository. Modify the
.git/configfile to include a Node.js payload in a standard field. - The Payload: We use the
require('child_process')primitive, which is available in the n8n execution environment.
[remote "origin"]
url = https://{{require('child_process').execSync('cat /etc/passwd').toString()}}@github.com/evil/repo.git- The Trigger: In n8n, create a workflow with a Git Node set to
List Config. Connect it to a Debug Node (or any node that references the output). - Execution: When the Debug node tries to resolve
{{ $json["remote.origin.url"] }}, the engine parses the inner injection.
The result isn't just a string; it's the output of the command. You have effectively turned a configuration reader into a webshell.
The Research: Bypassing the Fix
As a researcher, looking at the patch (Commit 936c06cf), I see a potential logical gap. The patch explicitly applies sanitizeUrl to remote.origin.url and remote.origin.pushurl.
// Selective sanitization?
if (config.values['remote.origin.url']) {
config.values['remote.origin.url'] = sanitizeUrl(config.values['remote.origin.url']);
}However, Git config files are flexible. What about remote.upstream.url? What about submodule.name.url? Or even standard fields like user.email? If the Git node outputs all keys found in the config file using a spread operator or a loop, and only sanitizes the "known bad" ones, the vulnerability persists.
If I were attacking a "patched" version, I would immediately fuzz other Git configuration keys. If n8n displays user.name and I set my git username to {{require('fs').writeFileSync(...)}}, I might still achieve RCE. This selective patching strategy is often fragile against creative attackers.
The Impact: Why You Should Care
This is a CVSS 9.4 for a reason. n8n is often the "brain" of an organization's operations. It holds API keys for Stripe, Slack, AWS, CRMs, and databases.
If an attacker gains RCE on the n8n host:
- Credential Theft: They can dump the n8n database or environment variables to steal every connected API key.
- Lateral Movement: n8n usually sits inside the internal network to talk to internal databases. The host becomes a pivot point.
- Supply Chain Poisoning: The attacker can modify other workflows to inject malicious logic into business processes (e.g., "When a new invoice is created, send a copy to my email").
This isn't just "I hacked a server." It's "I hacked your business logic."
Mitigation: Stopping the Bleeding
The immediate fix is obvious: Update. n8n versions 1.123.17 and 2.5.2 include the patch. But let's look at defense-in-depth, because relying on a regex-based URL sanitizer is risky business.
Strategic Defense
- Isolate Execution: Run n8n in a strictly sandboxed container. Do not mount the host Docker socket (
/var/run/docker.sock) unless absolutely necessary. If you do, an RCE in n8n is root on the host. - Least Privilege: The user running the n8n process should not have access to sensitive system files.
- Sanitize Inputs: If you are automating Git operations, ensure the repositories you clone are trusted. Don't let your marketing automation clone arbitrary URLs provided by external users.
For the developers out there: Stop using eval (or its moral equivalents) on data you didn't create. If you must have a template engine, ensure the context allows strictly for data substitution, not arbitrary code execution.
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n-io | < 1.123.17 | 1.123.17 |
n8n n8n-io | < 2.5.2 | 2.5.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-913 |
| Attack Vector | Network |
| CVSS Score | 9.4 (Critical) |
| Impact | Remote Code Execution (RCE) |
| Vulnerable Component | Git Node (List Config operation) |
| Exploit Complexity | Low |
MITRE ATT&CK Mapping
Improper Control of Dynamically-Managed Code Resources
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.