Axeploit
Axeploit
← Back to posts

12 min read

One Schema Name, Every Stored Credential: CVE-2026-33696 and the n8n Blast Radius

By Jason Miller

Filed under DAST & Scanning

CVE-2026-33696 scores 9.4 Critical from GitHub as CNA (NIST puts it at 8.8 High under CVSS 3.1), and it earns the number: any n8n user who can create or edit a workflow can turn a single custom field into remote code execution as the n8n process user. Simon Koeck, who found the bug, already published the vulnerable code, the __proto__ walkthrough, and a working one-curl PoC, and I am not going to pretend to out-detail the person who found it. What his writeup skips, and what the NVD mirrors below it add nothing on, is what a defender actually needs: the XML node half of the CVE, what the fix has to get right, how to hunt for exploitation, and why an n8n instance is close to the worst box on your network to lose.

The chain, compressed (and why the guard is not a guard)

The exploit chain: one webhook POST to RCE

The GSuiteAdmin node's Custom Fields section, used in the user create and update operations, takes schema name, field name, and value straight from workflow configuration. An attacker with editor access controls all three. The code does this, identically at lines 520-521 and 802-803:

javascript
customSchemas[schemaName] ??= {};
(customSchemas[schemaName] as IDataObject)[fieldName] = value;

Set schemaName to __proto__ and two things fall out. The ??= reads customSchemas["__proto__"], which invokes the prototype getter and returns Object.prototype, a truthy value, so the "guard" assignment never happens. Then the second line writes your field name and value directly onto Object.prototype. Every plain object created in that process afterward inherits the property.

Koeck's escalation is the elegant part. A downstream Git node calls simple-git, whose .env() allocates a plain {} that now carries the polluted key. Node.js spawn() copies inherited properties into the child process environment, and git executes GIT_SSH_COMMAND as a shell command when cloning an SSH-style URL. Webhook to GSuiteAdmin to Git. One POST.

Two operational details matter more than the cleverness. First, the GSuiteAdmin node in the PoC fails at the Google API call, but the pollution happens before that request goes out. A failed node in your execution log does not mean a failed attack. Second, pollution survives until the process restarts, which brings us to the failure mode nobody demos.

Pollution alone is a kill switch

Even without the Git gadget, writing junk onto Object.prototype wrecks the instance. TypeORM's buildWhere iterates with for...in, picks up the extra prototype properties, and throws EntityPropertyNotFoundError on every database query. The UI goes unresponsive, all workflow executions fail, and only a full restart clears it.

So a clumsy attacker, or a researcher replaying the PoC against the wrong host, takes your automation platform down whether or not they get a shell. It also hands you a retrospective indicator, which we will use below.

The XML node half of the CVE

The NVD record and the vendor advisory (GHSA-mxrg-77hm-89hv) cover both the XML and GSuiteAdmin nodes. Nearly everything written about this CVE describes only GSuiteAdmin. GitLab's advisory database entry is titled "Prototype Pollution in XML and GSuiteAdmin node parameters lead to RCE" and then its body text covers GSuiteAdmin alone. Koeck notes the GSuiteAdmin chain reuses "the exact same gadget" from a separate XML node report of his, and that second report is not part of the public material I have seen.

This matters for two reasons. If you concluded you are safe because nobody on your team touches the Google Workspace admin node, check your workflows for the XML node too. And look at the shape of the vendor's temporary mitigation: adding n8n-nodes-base.xml to NODES_EXCLUDE disables the XML node only. The advisory itself caveats that the interim steps do not fully remediate the risk, and GSuiteAdmin stays reachable. The only real fix is the patched release.

What the fix has to get right

Patch targets by release line (2.14.0 is a trap)

Fixed versions are 2.14.1, 2.13.3, and 1.123.27. Affected ranges: everything below 1.123.27, the 2.x line from 2.0.0-rc.0 up to (but not including) 2.13.3, and exactly 2.14.0. That last one is a trap. If you upgraded to 2.14.0 because it was the newest, you are still vulnerable. Verify the exact version string, not the major line.

Koeck recommends rejecting __proto__, constructor, and prototype on the schema name, or building customSchemas with Object.create(null) so there is no prototype to poison. He also points out that n8n's codebase already ships a deepMerge utility with prototype pollution guards, which this node simply was not using. My position: the blocklist is the minimum bar, not the fix to aspire to. Key-name blocklists decay, because every future code path that writes user-controlled keys onto plain objects has to remember to apply one. The durable pattern is to stop using plain objects as maps for untrusted keys (null-prototype objects or Map) and route all merging through the guarded utility.

Verify the fix on staging. Replay the published PoC shape against a patched instance: schema name __proto__, field name GIT_SSH_COMMAND, any value. You want two results: the instance survives without EntityPropertyNotFoundError, and a follow-up check shows Object.prototype clean. And restart the instance after patching regardless, because applying a package update does not unpollute a running process.

Hunting CVE-2026-33696 on your own instance

SentinelOne's entry lists solid detection ideas. Here is how I would operationalize them.

Workflow and log audit

n8n persists workflow definitions in its database, so a pollution attempt leaves a durable artifact. Sweep for the keys:

sql
SELECT id, name FROM workflow_entity
WHERE nodes LIKE '%__proto__%'
   OR nodes LIKE '%constructor%'
   OR nodes LIKE '%prototype%';

Table and column names differ between the default SQLite setup and Postgres deployments, so adapt to your schema. Expect constructor to be noisy; __proto__ almost never appears in a legitimate workflow.

Then correlate two signals from the writeup itself: a GSuiteAdmin node failing at its Google API call (the PoC fails there by design, after polluting) followed by a Git node execution in the same run, and EntityPropertyNotFoundError anywhere in server logs, which means something polluted the prototype badly enough to break TypeORM. If either hits, treat every credential stored in that instance as compromised. The n8n process holds the encryption key for all of them.

Process and egress signals

The Git node spawns git legitimately, so git alone is not your alert. What you want is the n8n process spawning things it has no business spawning:

yaml
- rule: n8n process spawning a shell
  condition: >
    proc.pname = node and
    proc.name in (sh, bash, dash, ssh, curl, wget, base64)
  output: "n8n spawned unexpected child: %proc.cmdline"
  priority: WARNING

Baseline first if you use Execute Command nodes, since those spawn shells by design. Also watch process environments: a git child carrying GIT_SSH_COMMAND with shell metacharacters is close to a smoking gun. On egress, n8n legitimately talks to a lot of API endpoints, which makes naive allowlisting painful, but connections to new destinations from the n8n host deserve a look.

Edge filtering on webhooks

The published entry point is a webhook POST. If your n8n webhooks sit behind a reverse proxy or WAF, log request bodies containing __proto__ or GIT_SSH_COMMAND. I would alert rather than block at first: constructor and prototype show up in legitimate JSON often enough to cause pain, while a quoted "__proto__" key is rare. Tighten after a week of baselining.

"It needs authentication" is thinner than it sounds

The pushback I expect: PR:L in the vector means this is an insider problem, and our editors are trusted. Three answers. First, low-code platforms exist so non-engineers can build automation, which means workflow editor rights get handed out far more broadly than SSH access ever would. The chain needs permission to create or modify workflows, nothing more. Second, the PoC is public and costs one curl. CISA-ADP's SSVC assessment on the NVD record lists exploitation as none observed so far, with technical impact marked total, and "none observed" has a short shelf life once working exploit code circulates. Third, credential access from a low-privilege n8n account does not even require this bug. CVE-2026-33663, patched in the same window, let member-level users read plaintext generic HTTP credentials (httpBasicAuth, httpHeaderAuth, httpQueryAuth) in Community Edition, and CVE-2026-33660 turned the Merge node's "Combine by SQL" mode into arbitrary local file read and RCE via AlaSQL. The pattern across all three is the point: n8n's permission model quietly assumes editors can be trusted with the server, and this CVE prices that assumption.

The strategic problem: n8n is a credential vault

Strip away the gadget and the strategic fact is one sentence: the n8n process holds the encryption key for every credential stored in it, so code execution as the n8n user is full credential theft. Koeck states this plainly, and it applies across self-hosted, worker mode, and Cloud deployments.

Now look at what a typical instance accumulates: Google Workspace admin access (the vulnerable node exists to administer your directory), repository tokens, CRM and payment API keys, OAuth grants, webhook endpoints facing the internet by design. n8n is also becoming the default plumbing for AI agent workflows, which adds LLM API keys and tool-use tokens to the same keyring while multiplying the webhook surface those agents call in through. One process, one keyring, dozens of upstream trusts.

So treat the platform like the vault it functionally is. Keep the editor list short and reviewed. Run n8n as a dedicated low-privilege user, because the RCE lands as whatever user you chose. Store as few long-lived credentials in n8n as you can get away with, preferring short-lived OAuth grants over static keys where the integration allows it. Segment egress so the host reaches the APIs it needs and nothing else. None of this is specific to CVE-2026-33696, which is exactly why it will still protect you when the next one lands. Koeck found this bug by grepping the nodes-base package for user-supplied strings used as property keys without prototype checks, and he says GSuiteAdmin "stood out immediately." A codebase that large has more property-key sinks. Assume more will be found.

This week's checklist

  1. Check your exact version and upgrade to 2.14.1, 2.13.3, or 1.123.27. Being on 2.14.0 does not save you.
  2. If patching has to wait, restrict workflow create and edit permissions to fully trusted users and set NODES_EXCLUDE="n8n-nodes-base.xml", knowing the advisory says these steps do not fully remediate.
  3. Sweep the workflow table for __proto__, constructor, and prototype.
  4. Search logs for EntityPropertyNotFoundError and for failed GSuiteAdmin executions followed by Git nodes.
  5. Alert on unexpected child processes of the n8n process and on new egress destinations.
  6. If anything hits, rotate every credential stored in the instance.

Key takeaways

  • CVE-2026-33696 is authenticated prototype pollution in n8n's GSuiteAdmin and XML nodes. One custom field named __proto__ writes onto Object.prototype, and a downstream Git node converts it to RCE via GIT_SSH_COMMAND. Fixed in 2.14.1, 2.13.3, and 1.123.27; 2.14.0 is specifically still vulnerable.
  • A failed exploit still bricks the instance. TypeORM chokes on polluted keys until restart, which makes EntityPropertyNotFoundError a high-signal IoC.
  • Hunt with what you already have: a keyword sweep of stored workflows, execution-log correlation, child process alerts on the n8n process, and egress monitoring.
  • "Authenticated" is weak comfort. Editor rights are broad by design in low-code tools, the PoC is one curl, and CVE-2026-33663 shows member-level credential theft without any RCE.
  • The durable fix is architectural. n8n holds the key to every stored credential, so minimize what you store in it, restrict editors, and segment egress. Patching closes this bug, not the blast radius.
Get started

Integrate Axeploit into your workflow today