n8n - ✅(Solved) Fix Webhook node: `sensitiveOutputFields` regression breaks workflows that read auth headers (e.g. JWT/HMAC verification) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
n8n-io/n8n#29819Fetched 2026-05-06 06:35:09
View on GitHub
Comments
1
Participants
2
Timeline
10
Reactions
0
Timeline (top)
labeled ×4mentioned ×2subscribed ×2commented ×1

Error Message

  • error: all

Root Cause

Validating Microsoft Bot Framework JWT tokens for a Teams bot. The Bot Framework signs every incoming request with a JWT in the Authorization header, and the bot is required to verify that signature against the published JWKS before responding. After upgrading, the Code node that previously did this verification fails with 'dict' object has no attribute 'lstrip' because $json.headers.authorization is now the placeholder dict instead of the bearer token string.

Fix Action

Fixed

PR fix notes

PR #29825: fix(Webhook Node): Add opt-out for sensitiveOutputFields masking

Description (problem / solution / changelog)

Summary

Closes #29819.

The Webhook node hardcodes sensitiveOutputFields: ['headers.authorization', 'headers.cookie'], which NodeDefinedFieldRedactionStrategy applies unconditionally — even on the reveal path and even when the workflow's redaction policy is none. The redacted dict propagates to downstream nodes as runtime data, so workflows that previously read the auth header for JWT signature verification, HMAC verification, or Bot Framework token validation break on upgrade. There is currently no per-workflow or per-node opt-out short of disabling the entire redaction module instance-wide.

This PR adds a node-instance-scoped escape hatch so the security-by-default behaviour stays in place for every existing workflow, while users with a legitimate need to inspect the raw auth header can opt their specific Webhook node in.

Use case

The motivating example from the issue: validating a Microsoft Bot Framework JWT against the published JWKS in a Code node. The Bot Framework signs every inbound request with a JWT in the Authorization header, and the bot is required to verify that signature before responding. The same shape applies to Slack, GitHub, Stripe, and Shopify HMAC verifications and any pattern where a downstream node reads the bearer token or the Cookie header as a string.

Design

A two-piece change in the redaction protocol:

  1. INodeTypeDescription.sensitiveOutputFieldsOptOutPath?: string (packages/workflow). A node author may declare a dot-notation path on INode.parameters. When that path resolves to strictly the boolean true on a given node instance, NodeDefinedFieldRedactionStrategy.buildSensitiveFieldsMap skips that node when building the redaction map.

    • Opt-in by node author: every existing node leaves the field unset, so the prior "always redacted, never revealable" guarantee continues to hold for all of them.
    • Opt-in by node instance: even on a node that declares the path, the per-instance default is to redact.
    • Fail-safe: missing keys, non-record intermediates, falsey values, and truthy non-booleans (e.g. the string "true") all leave redaction enabled.
  2. Webhook node (packages/nodes-base/nodes/Webhook). The Webhook node sets sensitiveOutputFieldsOptOutPath: 'options.exposeAuthHeaders' and adds an Expose Authentication Headers boolean inside its existing Options collection, defaulted to false. The option's description spells out the security trade-off (anyone with execution-read access on the workflow will see the headers in execution data).

Parameter UI shape

{
    displayName: 'Expose Authentication Headers',
    name: 'exposeAuthHeaders',
    type: 'boolean',
    default: false,
    description:
        "Whether to keep the Authorization and Cookie request headers in this node's output instead of redacting them. Enable only when downstream nodes need the raw values (e.g. JWT signature or HMAC verification). Anyone with execution-read access on the workflow will be able to see these headers in execution data.",
},

The opt-out is wired into the description as:

sensitiveOutputFields: ['headers.authorization', 'headers.cookie'],
sensitiveOutputFieldsOptOutPath: 'options.exposeAuthHeaders',

Why this shape over the alternatives in the issue

The issue suggests three approaches in priority order. I went with #2 (node-level toggle) for these reasons:

  • #1 (honour workflow Do not redact) would walk back the explicit guarantee from #26546 that node-declared sensitive fields are always redacted regardless of workflow policy. Workflow-level redaction settings and node-author-declared sensitive fields are deliberately decoupled so that "redact most fields but reveal everything in this workflow" remains a coherent permission model. Tying the two together would change the semantics for every node, not just Webhook.
  • #3 (typeVersion bump that defaults pre-existing instances to un-redacted) would silently un-redact existing webhooks on upgrade, which is a lateral regression from the user perspective: workflows that should keep redacting headers (the security default the original PR was added for) suddenly stop. A typeVersion bump where the new version defaults to redacting and the old version defaults to un-redacting would also leak the old behaviour forever for any pre-2.18 webhook, undoing the fix.
  • #2 (node-level toggle) preserves security-by-default for every existing instance, surfaces the trade-off in the UI text, and keeps the opt-out scoped to the one node-instance that needs it. Generic enough that other nodes can opt in to the same pattern without each baking their own toggle plumbing.

Test coverage

packages/cli/src/modules/redaction/executions/strategies/__tests__/node-defined-field-redaction.strategy.test.ts (sensitiveOutputFieldsOptOutPath describe block, 5 new cases):

  1. skips redaction when the per-instance opt-out parameter is true — happy path: opt-out true leaves headers.authorization as the original Bearer secret string.
  2. still redacts when the opt-out parameter is false — explicit-false still redacts to the marker.
  3. still redacts when the opt-out parameter is missing entirely (default) — missing key still redacts.
  4. still redacts when the opt-out parameter is a truthy non-boolean (fail-safe) — string "true" does not opt out.
  5. does not affect other nodes that lack an opt-out path on their description — a Webhook with the opt-out path opts out, while a sibling node without the path on its description keeps redacting even when the parameter is set on its instance.

packages/nodes-base/nodes/Webhook/test/Webhook.test.ts (sensitiveOutputFields describe block, 2 new cases):

  1. declares the opt-out parameter path so users can expose auth headers when needed — asserts description.sensitiveOutputFieldsOptOutPath === 'options.exposeAuthHeaders'.
  2. exposes the opt-out as a boolean option, defaulted to false (security-by-default) — asserts the new option exists in the Options collection with type: 'boolean' and default: false.

Local results:

  • pnpm --filter n8n-nodes-base test -- nodes/Webhook — 64/64 passed.
  • pnpm --filter n8n test -- modules/redaction — 87/87 passed (all 4 redaction suites including the strategy and orchestrator).
  • pnpm build --filter='n8n-workflow', --filter='n8n-nodes-base', --filter='n8n' — all green.
  • eslint nodes/Webhook — 0 errors.

Related Linear tickets, Github issues, and Community forum posts

Review / Merge checklist

  • PR title and summary are descriptive.
  • Docs updated or follow-up ticket created. Suggested doc edit: add a paragraph to https://docs.n8n.io/workflows/executions/execution-data-redaction/ noting the Webhook node now exposes a per-instance Expose Authentication Headers toggle for downstream JWT/HMAC verification.
  • Tests included.
  • PR Labeled with release/backport (worth considering — the regression breaks Bot Framework / HMAC / Stripe / Shopify webhooks on upgrade).

Changed files

  • packages/cli/src/modules/redaction/executions/strategies/__tests__/node-defined-field-redaction.strategy.test.ts (modified, +197/-2)
  • packages/cli/src/modules/redaction/executions/strategies/node-defined-field-redaction.strategy.ts (modified, +33/-2)
  • packages/nodes-base/nodes/Webhook/Webhook.node.ts (modified, +1/-0)
  • packages/nodes-base/nodes/Webhook/description.ts (modified, +8/-0)
  • packages/nodes-base/nodes/Webhook/test/Webhook.test.ts (modified, +18/-0)
  • packages/workflow/src/interfaces.ts (modified, +13/-0)

Code Example

{
  "__redacted": true,
  "reason": "node_defined_field",
  "canReveal": false
}
RAW_BUFFERClick to expand / collapse

Bug Description

This is a regression. Before the execution data redaction module was introduced, workflows could read headers.authorization and headers.cookie from the Webhook node as normal strings, allowing custom auth verification (JWT signature checks, HMAC signature validation, etc.) in downstream nodes. After the redaction module shipped and the Webhook node added a hardcoded sensitiveOutputFields: ['headers.authorization', 'headers.cookie'] declaration (Webhook.node.ts), these fields are replaced at runtime with a redacted placeholder dict:

{
  "__redacted": true,
  "reason": "node_defined_field",
  "canReveal": false
}

Any workflow that previously read those headers as strings now fails — typically with errors like 'dict' object has no attribute 'lstrip' in a Code node, or expression-resolution failures elsewhere. Workflows that worked on prior versions stop working after upgrade with no migration path and no opt-out short of disabling the entire redaction module instance-wide.

Why this is a bug, not a feature request

To be clear about the framing: the redaction feature itself is a legitimate, valuable addition. The bug is that it ships with no opt-out for the hardcoded blocklist, which silently changes the runtime data contract of the Webhook node and breaks pre-existing workflows.

Specifically:

  1. It's a runtime data change, not a UI change. The redacted dict flows to downstream nodes, not just to the editor view. This breaks executing code, not just debugging.
  2. It's not gated by the per-workflow toggle. Setting both "Redact production execution data" and "Redact manual execution data" to "Default - Do not redact" in workflow settings has no effect — sensitiveOutputFields redaction applies regardless. The docs at Execution data redaction confirm this: "n8n always redacts these fields and prevents revealing them, even for users with reveal access."
  3. It's not gated by a version bump on the node. The Webhook node typeVersion didn't increment to signal a breaking change, so existing workflows pick up the new behavior on instance upgrade with no warning.
  4. The only opt-out is instance-wide. Disabling N8N_ENABLE_EXECUTION_REDACTION turns off the whole feature for every workflow on the instance — unworkable for any organization that wants redaction on most workflows but needs auth headers in a specific one.

A redaction feature that silently breaks existing workflows on upgrade, with no per-workflow or per-node opt-out, is a regression regardless of the security intent behind it.

Use case that's blocked

Validating Microsoft Bot Framework JWT tokens for a Teams bot. The Bot Framework signs every incoming request with a JWT in the Authorization header, and the bot is required to verify that signature against the published JWKS before responding. After upgrading, the Code node that previously did this verification fails with 'dict' object has no attribute 'lstrip' because $json.headers.authorization is now the placeholder dict instead of the bearer token string.

This pattern isn't unique to Bot Framework — Slack, GitHub, Stripe, Shopify, and most webhook senders that use bearer tokens, HMAC signatures over the auth header, or signed JWTs require the receiver to inspect the auth header to validate the request. Any workflow doing this kind of verification is broken by this change.

Related

Suggested fix

In priority order:

  1. Honor the workflow's "Do not redact" setting for sensitiveOutputFields declared by built-in nodes. This restores prior behavior for workflows that explicitly opt out, while keeping the secure default for workflows that don't change the setting.
  2. Add a node-level toggle on the Webhook node (e.g., "Expose Authentication Headers", default: false) that bypasses the hardcoded blocklist when enabled. This is roughly what PR #20783 was building toward.
  3. Bump the Webhook node typeVersion so existing workflows continue to receive un-redacted headers and only newly-created Webhook nodes default to redacting. This is the standard n8n pattern for behavior changes and would have prevented the regression in the first place.

The PR #20783 conversation already covers most of the design tradeoffs.

Environment

  • n8n version: 2.18.7
  • Database: Postgres
  • Running via: Docker
  • OS: Docker

Note on framing

I'm filing this as a bug rather than a feature request because the change broke previously-working behavior on instance upgrade with no migration path. If the team would prefer to track this as a feature request for a configurable opt-out, that's fine — but the underlying issue is that a working workflow stopped working without a typeVersion bump or any other signal that a breaking change had occurred.

To Reproduce

  1. On a self-hosted instance with N8N_ENABLE_EXECUTION_REDACTION=true
  2. Create a workflow with a Webhook trigger (Authentication: None) followed by a Code node
  3. In workflow Settings, set both redaction options to "Default - Do not redact"
  4. Send a request with an Authorization: Bearer <token> header
  5. In the Code node, attempt to read $json.headers.authorization — it's the redacted dict, not the string the workflow used to receive

Expected behavior

The previous behavior: headers.authorization and headers.cookie flow as strings to downstream nodes, with the workflow-level redaction setting respected. At minimum, one of:

  • The workflow-level "Do not redact" setting takes precedence over sensitiveOutputFields
  • A node-level option on the Webhook node to disable sensitiveOutputFields redaction (the approach attempted in PR #20783)
  • A typeVersion bump on the Webhook node so existing workflows keep their prior behavior and only new nodes default to redacting

Actual

sensitiveOutputFields is applied unconditionally whenever the redaction module is initialized. Existing workflows are silently broken on upgrade with no opt-out short of disabling the entire redaction module instance-wide.

Debug Info

Debug info

core

  • n8nVersion: 2.18.7
  • platform: docker (self-hosted)
  • nodeJsVersion: 24.14.1
  • nodeEnv: production
  • database: postgres
  • executionMode: scaling (single-main)
  • concurrency: -1
  • license: enterprise (production)
  • consumerId: c8093090-c197-434f-847b-d1b2c756f509

storage

  • success: all
  • error: all
  • progress: false
  • manual: true
  • binaryMode: s3

pruning

  • enabled: true
  • maxAge: 336 hours
  • maxCount: 10000 executions

client

  • userAgent: mozilla/5.0 (macintosh; intel mac os x 10_15_7) applewebkit/537.36 (khtml, like gecko) chrome/147.0.0.0 safari/537.36
  • isTouchDevice: false

Generated at: 2026-05-05T16:48:52.444Z

Operating System

Docker

n8n Version

2.18.7

Node.js Version

24.14.1

Database

PostgreSQL

Execution mode

queue

Hosting

n8n cloud

extent analysis

TL;DR

To fix the regression caused by the execution data redaction module, add a node-level toggle on the Webhook node to bypass the hardcoded blocklist for sensitive fields like headers.authorization and headers.cookie.

Guidance

  • Identify workflows that rely on reading headers.authorization and headers.cookie as strings from the Webhook node.
  • Consider adding a node-level option to the Webhook node to opt-out of redacting these fields, similar to the approach proposed in PR #20783.
  • If possible, test the suggested fix by honoring the workflow's "Do not redact" setting for sensitiveOutputFields declared by built-in nodes.
  • Verify that the fix works by checking if the Code node can successfully read $json.headers.authorization as a string.

Example

No code snippet is provided as the issue is more related to configuration and node settings.

Notes

The suggested fix may not be applicable to all use cases, and a thorough testing of the proposed solution is necessary to ensure it does not introduce any security vulnerabilities.

Recommendation

Apply a workaround by adding a node-level toggle on the Webhook node to bypass the hardcoded blocklist, as this approach seems to be the most feasible solution given the constraints of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The previous behavior: headers.authorization and headers.cookie flow as strings to downstream nodes, with the workflow-level redaction setting respected. At minimum, one of:

  • The workflow-level "Do not redact" setting takes precedence over sensitiveOutputFields
  • A node-level option on the Webhook node to disable sensitiveOutputFields redaction (the approach attempted in PR #20783)
  • A typeVersion bump on the Webhook node so existing workflows keep their prior behavior and only new nodes default to redacting

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

n8n - ✅(Solved) Fix Webhook node: `sensitiveOutputFields` regression breaks workflows that read auth headers (e.g. JWT/HMAC verification) [1 pull requests, 1 comments, 2 participants]