mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +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:",
|
||||
"https-proxy-agent": "catalog:",
|
||||
"iconv-lite": "catalog:",
|
||||
"jsdom": "23.0.1",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"lodash": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sandboxHtmlResponse should handle HTML with special characters 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"
|
||||
exports[`sandboxHtmlResponse should always sandbox if forceSandbox is true 1`] = `
|
||||
"<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;"
|
||||
allowtransparency="true"></iframe>"
|
||||
`;
|
||||
|
||||
exports[`sandboxHtmlResponse should handle empty HTML 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"
|
||||
exports[`sandboxHtmlResponse should handle HTML with special characters 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"
|
||||
style="position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;"
|
||||
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;"
|
||||
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();
|
||||
});
|
||||
|
||||
it('should handle empty HTML', () => {
|
||||
const html = '';
|
||||
expect(sandboxHtmlResponse(html)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle HTML with special characters', () => {
|
||||
const html = '<p>Special characters: <>&"\'</p>';
|
||||
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', () => {
|
||||
@@ -143,7 +170,7 @@ describe('bufferEscapeHtml', () => {
|
||||
|
||||
describe('createHtmlSandboxTransformStream', () => {
|
||||
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 () => {
|
||||
const input = Buffer.from('Hello & "World"', 'utf8');
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import type { TransformCallback } from 'stream';
|
||||
import { Transform } from 'stream';
|
||||
|
||||
/**
|
||||
* Sandboxes the HTML response to prevent possible exploitation. Embeds the
|
||||
* response in an iframe to make sure the HTML has a different origin.
|
||||
* Checks if the given string contains HTML.
|
||||
*/
|
||||
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;
|
||||
if (typeof data !== 'string') {
|
||||
text = JSON.stringify(data);
|
||||
@@ -13,6 +33,10 @@ export const sandboxHtmlResponse = <T>(data: T) => {
|
||||
text = data;
|
||||
}
|
||||
|
||||
if (!forceSandbox && !hasHtml(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Escape & and " as mentioned in the spec:
|
||||
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element
|
||||
const escapedHtml = text.replaceAll('&', '&').replaceAll('"', '"');
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -1739,6 +1739,9 @@ importers:
|
||||
iconv-lite:
|
||||
specifier: 'catalog:'
|
||||
version: 0.6.3
|
||||
jsdom:
|
||||
specifier: 23.0.1
|
||||
version: 23.0.1
|
||||
jsonwebtoken:
|
||||
specifier: 'catalog:'
|
||||
version: 9.0.2
|
||||
|
||||
Reference in New Issue
Block a user