n8n/packages/cli/src/webhooks/webhook-response-headers.ts
n8n-assistant[bot] 562d867483
chore: Bundle 2026-W7 (#26214)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
Co-authored-by: yehorkardash <yehor.kardash@n8n.io>
Co-authored-by: James Gee <1285296+geemanjs@users.noreply.github.com>
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Stephen Wright <sjw948@gmail.com>
Co-authored-by: oleg <me@olegivaniv.com>
Co-authored-by: Albert Alises <albert.alises@gmail.com>
Co-authored-by: Danny Martini <danny@n8n.io>
2026-02-25 07:08:58 +00:00

71 lines
2.1 KiB
TypeScript

import { Logger } from '@n8n/backend-common';
import { Container } from '@n8n/di';
import type { Response } from 'express';
import { validateHeaderName, validateHeaderValue } from 'node:http';
import { ensureError } from 'n8n-workflow';
/**
* The headers object that node's `responseHeaders` property can return
*/
export type WebhookNodeResponseHeaders = {
entries?: Array<{
name: string;
value: string;
}>;
};
/** Headers that users are not allowed to set via webhook response config */
const PROTECTED_HEADERS = new Set(['content-security-policy']);
/** Response headers. Keys are always lower-cased. Invalid headers are silently skipped. */
export class WebhookResponseHeaders {
private headers = new Map<string, string>();
/** Create an instance from a plain object, validating each entry. */
static fromObject(obj: object): WebhookResponseHeaders {
const instance = new WebhookResponseHeaders();
instance.addFromObject(obj);
return instance;
}
/** Add a single header. Silently skips invalid or protected headers. */
set(name: string, value: string): void {
const lowerName = name.toLowerCase();
if (PROTECTED_HEADERS.has(lowerName)) return;
try {
validateHeaderName(lowerName);
validateHeaderValue(lowerName, value);
} catch (e) {
Container.get(Logger).warn('Dropping invalid webhook response header', {
headerName: name,
error: ensureError(e).message,
});
return;
}
this.headers.set(lowerName, value);
}
/** Add headers from a plain object (e.g. IDataObject from RespondToWebhook node). */
addFromObject(obj: object): void {
for (const [name, value] of Object.entries(obj)) {
this.set(name, String(value));
}
}
/** Add headers from a webhook node's `responseHeaders` parameter. */
addFromNodeHeaders(nodeHeaders: WebhookNodeResponseHeaders): void {
if (nodeHeaders.entries === undefined) return;
for (const entry of nodeHeaders.entries) {
this.set(entry.name, entry.value);
}
}
/** Apply all validated headers to an Express response. */
applyToResponse(res: Response): void {
if (this.headers.size === 0) return;
res.setHeaders(this.headers);
}
}