mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
fix(n8n Form Node): Add html table tags to allowedTags, CSP headers on form completion, free text sanitization removed (#19446)
This commit is contained in:
@@ -89,6 +89,259 @@ describe('FormTrigger, sanitizeHtml', () => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow table elements and preserve structure', () => {
|
||||
const tableTestCases = [
|
||||
{
|
||||
html: '<table><tr><td>Cell content</td></tr></table>',
|
||||
expected: '<table><tr><td>Cell content</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><thead><tr><th>Name</th><th>Age</th></tr></thead><tbody><tr><td>John</td><td>30</td></tr><tr><td>Jane</td><td>25</td></tr></tbody></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th>Name</th><th>Age</th></tr></thead><tbody><tr><td>John</td><td>30</td></tr><tr><td>Jane</td><td>25</td></tr></tbody></table>',
|
||||
},
|
||||
];
|
||||
|
||||
tableTestCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow tfoot elements and preserve table footer structure', () => {
|
||||
const tfootTestCases = [
|
||||
{
|
||||
html: '<table><tfoot><tr><td>Footer content</td></tr></tfoot></table>',
|
||||
expected: '<table><tfoot><tr><td>Footer content</td></tr></tfoot></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody><tfoot><tr><td>Footer</td></tr></tfoot></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody><tfoot><tr><td>Footer</td></tr></tfoot></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tfoot><tr><th>Total</th><td>$100</td></tr></tfoot></table>',
|
||||
expected: '<table><tfoot><tr><th>Total</th><td>$100</td></tr></tfoot></table>',
|
||||
},
|
||||
];
|
||||
|
||||
tfootTestCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve table cell attributes (colspan, rowspan, scope, headers)', () => {
|
||||
const cellAttributeTestCases = [
|
||||
{
|
||||
html: '<table><tr><td colspan="2">Spanning cell</td></tr></table>',
|
||||
expected: '<table><tr><td colspan="2">Spanning cell</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><th rowspan="3">Header</th><td>Data 1</td></tr><tr><td>Data 2</td></tr><tr><td>Data 3</td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><th rowspan="3">Header</th><td>Data 1</td></tr><tr><td>Data 2</td></tr><tr><td>Data 3</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><th scope="col">Column Header</th></tr><tr><th scope="row">Row Header</th><td>Data</td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><th scope="col">Column Header</th></tr><tr><th scope="row">Row Header</th><td>Data</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><th id="header1">Name</th><th id="header2">Age</th></tr><tr><td headers="header1">John</td><td headers="header2">30</td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><th>Name</th><th>Age</th></tr><tr><td headers="header1">John</td><td headers="header2">30</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><td colspan="2" rowspan="2">Complex cell</td><td>Simple cell</td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><td colspan="2" rowspan="2">Complex cell</td><td>Simple cell</td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
cellAttributeTestCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip malicious attributes from table cells while preserving allowed ones', () => {
|
||||
const maliciousCellTestCases = [
|
||||
{
|
||||
html: '<td onclick="alert(\'XSS\')" colspan="2" style="color: red;">Safe content</td>',
|
||||
expected: '<td colspan="2">Safe content</td>',
|
||||
},
|
||||
{
|
||||
html: '<th onmouseover="steal()" rowspan="3" class="malicious" scope="col">Header</th>',
|
||||
expected: '<th rowspan="3" scope="col">Header</th>',
|
||||
},
|
||||
{
|
||||
html: '<td headers="header1" data-evil="payload" onerror="hack()">Data</td>',
|
||||
expected: '<td headers="header1">Data</td>',
|
||||
},
|
||||
{
|
||||
html: '<th colspan="2" rowspan="2" onclick="javascript:alert(\'XSS\')" scope="colgroup">Multi-span header</th>',
|
||||
expected: '<th colspan="2" rowspan="2" scope="colgroup">Multi-span header</th>',
|
||||
},
|
||||
];
|
||||
|
||||
maliciousCellTestCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle complex table structures with tfoot and cell attributes', () => {
|
||||
const complexTableTestCases = [
|
||||
{
|
||||
html: '<table><thead><tr><th colspan="2" scope="colgroup">Sales Report</th></tr><tr><th scope="col">Product</th><th scope="col">Revenue</th></tr></thead><tbody><tr><th scope="row">Widget A</th><td>$1,000</td></tr><tr><th scope="row">Widget B</th><td>$2,000</td></tr></tbody><tfoot><tr><th scope="row">Total</th><td colspan="1">$3,000</td></tr></tfoot></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th colspan="2" scope="colgroup">Sales Report</th></tr><tr><th scope="col">Product</th><th scope="col">Revenue</th></tr></thead><tbody><tr><th scope="row">Widget A</th><td>$1,000</td></tr><tr><th scope="row">Widget B</th><td>$2,000</td></tr></tbody><tfoot><tr><th scope="row">Total</th><td colspan="1">$3,000</td></tr></tfoot></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tbody><tr><td rowspan="2">Multi-row cell</td><td>Cell 1</td></tr><tr><td>Cell 2</td></tr></tbody><tfoot><tr><td colspan="2">Footer spans both columns</td></tr></tfoot></table>',
|
||||
expected:
|
||||
'<table><tbody><tr><td rowspan="2">Multi-row cell</td><td>Cell 1</td></tr><tr><td>Cell 2</td></tr></tbody><tfoot><tr><td colspan="2">Footer spans both columns</td></tr></tfoot></table>',
|
||||
},
|
||||
];
|
||||
|
||||
complexTableTestCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove malicious attributes from table elements', () => {
|
||||
const maliciousTableCases = [
|
||||
{
|
||||
html: '<table onclick="alert(\'XSS\')" class="malicious"><tr><td>Content</td></tr></table>',
|
||||
expected: '<table><tr><td>Content</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<thead onmouseover="steal()" id="header"><tr><th onclick="hack()">Header</th></tr></thead>',
|
||||
expected: '<thead><tr><th>Header</th></tr></thead>',
|
||||
},
|
||||
{
|
||||
html: '<tbody style="background: red;" data-evil="payload"><tr><td onerror="malicious()">Data</td></tr></tbody>',
|
||||
expected: '<tbody><tr><td>Data</td></tr></tbody>',
|
||||
},
|
||||
{
|
||||
html: '<tr onload="alert(\'XSS\')" class="row"><td onblur="steal()" title="tooltip">Cell</td></tr>',
|
||||
expected: '<tr><td>Cell</td></tr>',
|
||||
},
|
||||
{
|
||||
html: '<th onclick="javascript:alert(\'XSS\')" style="color: red;">Header</th>',
|
||||
expected: '<th>Header</th>',
|
||||
},
|
||||
{
|
||||
html: '<td onmouseover="malicious()" data-payload="evil">Cell Data</td>',
|
||||
expected: '<td>Cell Data</td>',
|
||||
},
|
||||
];
|
||||
|
||||
maliciousTableCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle nested content within table elements', () => {
|
||||
const nestedTableCases = [
|
||||
{
|
||||
html: '<table><tr><td><strong>Bold</strong> and <em>italic</em> text</td></tr></table>',
|
||||
expected: '<table><tr><td><strong>Bold</strong> and <em>italic</em> text</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><thead><tr><th><a href="https://example.com">Link Header</a></th></tr></thead></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th><a href="https://example.com">Link Header</a></th></tr></thead></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tbody><tr><td><ul><li>Item 1</li><li>Item 2</li></ul></td></tr></tbody></table>',
|
||||
expected:
|
||||
'<table><tbody><tr><td><ul><li>Item 1</li><li>Item 2</li></ul></td></tr></tbody></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><td><code>code snippet</code> and <pre>preformatted text</pre></td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><td><code>code snippet</code> and <pre>preformatted text</pre></td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
nestedTableCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle malformed table structures gracefully', () => {
|
||||
const malformedTableCases = [
|
||||
{
|
||||
html: '<table><td>Cell without row</td></table>',
|
||||
expected: '<table><td>Cell without row</td></table>',
|
||||
},
|
||||
{
|
||||
html: '<thead><th>Header without table</th></thead>',
|
||||
expected: '<thead><th>Header without table</th></thead>',
|
||||
},
|
||||
{
|
||||
html: '<tbody><tr><td>Unclosed table',
|
||||
expected: '<tbody><tr><td>Unclosed table</td></tr></tbody>',
|
||||
},
|
||||
{
|
||||
html: '<tr><th>Mixed header</th><td>and data</td></tr>',
|
||||
expected: '<tr><th>Mixed header</th><td>and data</td></tr>',
|
||||
},
|
||||
];
|
||||
|
||||
malformedTableCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should prevent XSS attacks through table elements', () => {
|
||||
const xssTableCases = [
|
||||
{
|
||||
html: '<table><tr><td><script>alert("XSS")</script>Safe content</td></tr></table>',
|
||||
expected: '<table><tr><td>Safe content</td></tr></table>',
|
||||
},
|
||||
{
|
||||
html: '<thead><tr><th><img src="x" onerror="alert(\'XSS\')">Header</th></tr></thead>',
|
||||
expected: '<thead><tr><th><img src="x" />Header</th></tr></thead>',
|
||||
},
|
||||
{
|
||||
html: '<tbody><tr><td><a href="javascript:alert(\'XSS\')">Malicious Link</a></td></tr></tbody>',
|
||||
expected: '<tbody><tr><td><a>Malicious Link</a></td></tr></tbody>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><td><iframe src="javascript:alert(\'XSS\')"></iframe></td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><td><iframe referrerpolicy="strict-origin-when-cross-origin" allow="fullscreen; autoplay; encrypted-media"></iframe></td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
xssTableCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve complex table layouts', () => {
|
||||
const complexTableCases = [
|
||||
{
|
||||
html: '<table><thead><tr><th>Product</th><th>Price</th><th>Stock</th></tr></thead><tbody><tr><td>Widget A</td><td>$10.99</td><td>50</td></tr><tr><td>Widget B</td><td>$15.99</td><td>25</td></tr></tbody></table>',
|
||||
expected:
|
||||
'<table><thead><tr><th>Product</th><th>Price</th><th>Stock</th></tr></thead><tbody><tr><td>Widget A</td><td>$10.99</td><td>50</td></tr><tr><td>Widget B</td><td>$15.99</td><td>25</td></tr></tbody></table>',
|
||||
},
|
||||
{
|
||||
html: '<table><tr><th>Q1</th><th>Q2</th><th>Q3</th><th>Q4</th></tr><tr><td>100</td><td>150</td><td>200</td><td>175</td></tr></table>',
|
||||
expected:
|
||||
'<table><tr><th>Q1</th><th>Q2</th><th>Q3</th><th>Q4</th></tr><tr><td>100</td><td>150</td><td>200</td><td>175</td></tr></table>',
|
||||
},
|
||||
];
|
||||
|
||||
complexTableCases.forEach(({ html, expected }) => {
|
||||
expect(sanitizeHtml(html)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FormTrigger, formWebhook', () => {
|
||||
|
||||
Reference in New Issue
Block a user