mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-21 20:00:02 +00:00
fix(Webhook Node): Don't wrap response in an iframe if it doesn't have HTML (#17671)
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
"http-proxy-agent": "catalog:",
|
"http-proxy-agent": "catalog:",
|
||||||
"https-proxy-agent": "catalog:",
|
"https-proxy-agent": "catalog:",
|
||||||
"iconv-lite": "catalog:",
|
"iconv-lite": "catalog:",
|
||||||
|
"jsdom": "23.0.1",
|
||||||
"jsonwebtoken": "catalog:",
|
"jsonwebtoken": "catalog:",
|
||||||
"lodash": "catalog:",
|
"lodash": "catalog:",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`sandboxHtmlResponse should handle HTML with special characters 1`] = `
|
exports[`sandboxHtmlResponse should always sandbox if forceSandbox is true 1`] = `
|
||||||
"<iframe srcdoc="<p>Special characters: <>&"'</p>" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
"<iframe srcdoc="Hello World" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
||||||
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
allowtransparency="true"></iframe>"
|
allowtransparency="true"></iframe>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sandboxHtmlResponse should handle empty HTML 1`] = `
|
exports[`sandboxHtmlResponse should handle HTML with special characters 1`] = `
|
||||||
"<iframe srcdoc="" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
"<iframe srcdoc="<p>Special characters: <>&"'</p>" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
||||||
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
allowtransparency="true"></iframe>"
|
allowtransparency="true"></iframe>"
|
||||||
`;
|
`;
|
||||||
@@ -17,3 +17,21 @@ exports[`sandboxHtmlResponse should replace ampersands and double quotes in HTML
|
|||||||
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
allowtransparency="true"></iframe>"
|
allowtransparency="true"></iframe>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`sandboxHtmlResponse should sandbox even with no <body> tag 1`] = `
|
||||||
|
"<iframe srcdoc="<html><head><title>Test</title><script>alert("Hello")</script></head></html>" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
||||||
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
|
allowtransparency="true"></iframe>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`sandboxHtmlResponse should sandbox when outside <body> and <head> tags 1`] = `
|
||||||
|
"<iframe srcdoc="<html><head><title>Test</title></head><body></body><script>alert("Hello")</script></html>" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
||||||
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
|
allowtransparency="true"></iframe>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`sandboxHtmlResponse should sandbox when outside <html> tag 1`] = `
|
||||||
|
"<iframe srcdoc="<html><head><title>Test</title></head></html><script>alert("Hello")</script>" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
|
||||||
|
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||||
|
allowtransparency="true"></iframe>"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -24,15 +24,42 @@ describe('sandboxHtmlResponse', () => {
|
|||||||
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle empty HTML', () => {
|
|
||||||
const html = '';
|
|
||||||
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle HTML with special characters', () => {
|
it('should handle HTML with special characters', () => {
|
||||||
const html = '<p>Special characters: <>&"\'</p>';
|
const html = '<p>Special characters: <>&"\'</p>';
|
||||||
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['Hello World', 'Hello World'],
|
||||||
|
['< not html >', '< not html >'],
|
||||||
|
['# Test', '# Test'],
|
||||||
|
['', ''],
|
||||||
|
[123, '123'],
|
||||||
|
[null, 'null'],
|
||||||
|
])('should not sandbox if not html', (data, expected) => {
|
||||||
|
expect(sandboxHtmlResponse(data)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sandbox even with no <body> tag', () => {
|
||||||
|
const html = '<html><head><title>Test</title><script>alert("Hello")</script></head></html>';
|
||||||
|
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sandbox when outside <body> and <head> tags', () => {
|
||||||
|
const html =
|
||||||
|
'<html><head><title>Test</title></head><body></body><script>alert("Hello")</script></html>';
|
||||||
|
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sandbox when outside <html> tag', () => {
|
||||||
|
const html = '<html><head><title>Test</title></head></html><script>alert("Hello")</script>';
|
||||||
|
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always sandbox if forceSandbox is true', () => {
|
||||||
|
const text = 'Hello World';
|
||||||
|
expect(sandboxHtmlResponse(text, true)).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isHtmlRenderedContentType', () => {
|
describe('isHtmlRenderedContentType', () => {
|
||||||
@@ -143,7 +170,7 @@ describe('bufferEscapeHtml', () => {
|
|||||||
|
|
||||||
describe('createHtmlSandboxTransformStream', () => {
|
describe('createHtmlSandboxTransformStream', () => {
|
||||||
const getComparableHtml = (input: Buffer | string) =>
|
const getComparableHtml = (input: Buffer | string) =>
|
||||||
sandboxHtmlResponse(input.toString()).replace(/\s+/g, ' ');
|
sandboxHtmlResponse(input.toString(), true).replace(/\s+/g, ' ');
|
||||||
|
|
||||||
it('should wrap single chunk in iframe with proper escaping', async () => {
|
it('should wrap single chunk in iframe with proper escaping', async () => {
|
||||||
const input = Buffer.from('Hello & "World"', 'utf8');
|
const input = Buffer.from('Hello & "World"', 'utf8');
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
|
import { JSDOM } from 'jsdom';
|
||||||
import type { TransformCallback } from 'stream';
|
import type { TransformCallback } from 'stream';
|
||||||
import { Transform } from 'stream';
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sandboxes the HTML response to prevent possible exploitation. Embeds the
|
* Checks if the given string contains HTML.
|
||||||
* response in an iframe to make sure the HTML has a different origin.
|
|
||||||
*/
|
*/
|
||||||
export const sandboxHtmlResponse = <T>(data: T) => {
|
export const hasHtml = (str: string) => {
|
||||||
|
try {
|
||||||
|
const dom = new JSDOM(str);
|
||||||
|
return (
|
||||||
|
dom.window.document.body.children.length > 0 || dom.window.document.head.children.length > 0
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sandboxes the HTML response to prevent possible exploitation, if the data has HTML.
|
||||||
|
* If the data does not have HTML, it will be returned as is.
|
||||||
|
* Otherwise, it embeds the response in an iframe to make sure the HTML has a different origin.
|
||||||
|
*
|
||||||
|
* @param data - The data to sandbox.
|
||||||
|
* @param forceSandbox - Whether to force sandboxing even if the data does not contain HTML.
|
||||||
|
* @returns The sandboxed HTML response.
|
||||||
|
*/
|
||||||
|
export const sandboxHtmlResponse = <T>(data: T, forceSandbox = false) => {
|
||||||
let text;
|
let text;
|
||||||
if (typeof data !== 'string') {
|
if (typeof data !== 'string') {
|
||||||
text = JSON.stringify(data);
|
text = JSON.stringify(data);
|
||||||
@@ -13,6 +33,10 @@ export const sandboxHtmlResponse = <T>(data: T) => {
|
|||||||
text = data;
|
text = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!forceSandbox && !hasHtml(text)) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
// Escape & and " as mentioned in the spec:
|
// Escape & and " as mentioned in the spec:
|
||||||
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element
|
||||||
const escapedHtml = text.replaceAll('&', '&').replaceAll('"', '"');
|
const escapedHtml = text.replaceAll('&', '&').replaceAll('"', '"');
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -1739,6 +1739,9 @@ importers:
|
|||||||
iconv-lite:
|
iconv-lite:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.6.3
|
version: 0.6.3
|
||||||
|
jsdom:
|
||||||
|
specifier: 23.0.1
|
||||||
|
version: 23.0.1
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
|
|||||||
Reference in New Issue
Block a user