fix(n8n Form Trigger Node): Sanitize HTML for formNode (#13595)

This commit is contained in:
Dana 2025-03-03 14:28:54 +01:00 committed by GitHub
parent c1fe785174
commit 20dfaa3be6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 120 additions and 1 deletions

View file

@ -6,7 +6,7 @@ import {
type IWebhookResponseData,
} from 'n8n-workflow';
import { renderForm } from './utils';
import { renderForm, sanitizeHtml } from './utils';
export const renderFormNode = async (
context: IWebhookFunctions,
@ -42,6 +42,12 @@ export const renderFormNode = async (
) as string) || 'Submit';
}
for (const field of fields) {
if (field.fieldType === 'html') {
field.html = sanitizeHtml(field.html as string);
}
}
const appendAttribution = context.evaluateExpression(
`{{ $('${trigger?.name}').params.options?.appendAttribution === false ? false : true }}`,
) as boolean;

View file

@ -0,0 +1,113 @@
import { type Response } from 'express';
import { mock } from 'jest-mock-extended';
import {
type FormFieldsParameter,
type IWebhookFunctions,
type NodeTypeAndVersion,
} from 'n8n-workflow';
import { renderFormNode } from '../formNodeUtils';
describe('formNodeUtils', () => {
it('should sanitize custom html', async () => {
const executeFunctions = mock<IWebhookFunctions>();
executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any);
executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({
formTitle: 'Test Title',
formDescription: 'Test Description',
buttonLabel: 'Test Button Label',
});
const mockRender = jest.fn();
const formFields: FormFieldsParameter = [
{
fieldLabel: 'Custom HTML',
fieldType: 'html',
html: '<div>Test HTML</div>',
requiredField: false,
},
{
fieldLabel: 'Custom HTML',
fieldType: 'html',
html: '<script>Test HTML</script>',
requiredField: false,
},
{
fieldLabel: 'Custom HTML',
fieldType: 'html',
html: '<style>Test HTML</style>',
requiredField: false,
},
{
fieldLabel: 'Custom HTML',
fieldType: 'html',
html: '<style>Test HTML</style><div>hihihi</div><script>Malicious script here</script>',
requiredField: false,
},
];
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
const responseMock = mock<Response>({ render: mockRender } as any);
const triggerMock = mock<NodeTypeAndVersion>({ name: 'triggerName' } as any);
await renderFormNode(executeFunctions, responseMock, triggerMock, formFields, 'test');
expect(mockRender).toHaveBeenCalledWith('form-trigger', {
appendAttribution: true,
buttonLabel: 'Test Button Label',
formDescription: 'Test Description',
formDescriptionMetadata: 'Test Description',
formFields: [
{
defaultValue: '',
errorId: 'error-field-0',
html: '<div>Test HTML</div>',
id: 'field-0',
inputRequired: '',
isHtml: true,
label: 'Custom HTML',
placeholder: undefined,
},
{
defaultValue: '',
errorId: 'error-field-1',
html: '',
id: 'field-1',
inputRequired: '',
isHtml: true,
label: 'Custom HTML',
placeholder: undefined,
},
{
defaultValue: '',
errorId: 'error-field-2',
html: '',
id: 'field-2',
inputRequired: '',
isHtml: true,
label: 'Custom HTML',
placeholder: undefined,
},
{
defaultValue: '',
errorId: 'error-field-3',
html: '<div>hihihi</div>',
id: 'field-3',
inputRequired: '',
isHtml: true,
label: 'Custom HTML',
placeholder: undefined,
},
],
formSubmittedHeader: undefined,
formSubmittedText: 'Your response has been recorded',
formTitle: 'Test Title',
n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
testRun: true,
useResponseData: true,
validForm: true,
});
});
});