Skip to content

Commit f45d92e

Browse files
authored
fix: [#0] Adds warning for environemnt with unfrozen builtins (#1932)
1 parent 819d15b commit f45d92e

File tree

11 files changed

+97
-41
lines changed

11 files changed

+97
-41
lines changed

packages/@happy-dom/server-renderer/src/utilities/HelpPrinterRows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export default [
133133
'false'
134134
],
135135
[
136-
'--browser.suppressCodeGenerationFromStringsWarning',
136+
'--browser.suppressInsecureJavaScriptEnvironmentWarning',
137137
'',
138138
'boolean',
139139
'Suppresses the warning that is printed when code generation from strings is enabled at process level',

packages/@happy-dom/server-renderer/src/utilities/ProcessArgumentsParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export default class ProcessArgumentsParser {
4141
config.help = true;
4242
} else if (arg === '--browser.disableJavaScriptEvaluation') {
4343
config.browser.enableJavaScriptEvaluation = false;
44-
} else if (arg === '--browser.suppressCodeGenerationFromStringsWarning') {
45-
config.browser.suppressCodeGenerationFromStringsWarning = true;
44+
} else if (arg === '--browser.suppressInsecureJavaScriptEnvironmentWarning') {
45+
config.browser.suppressInsecureJavaScriptEnvironmentWarning = true;
4646
} else if (arg === '--browser.disableJavaScriptFileLoading') {
4747
config.browser.disableJavaScriptFileLoading = true;
4848
} else if (arg === '--browser.disableCSSFileLoading') {

packages/@happy-dom/server-renderer/test/ServerRendererBrowser.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('ServerRendererBrowser', () => {
3535
});
3636
const browser = new ServerRendererBrowser(
3737
ServerRendererConfigurationFactory.createConfiguration({
38-
browser: { suppressCodeGenerationFromStringsWarning: true }
38+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true }
3939
})
4040
);
4141
const results = await browser.render([{ url: 'https://example.com/gb/en/' }]);
@@ -73,7 +73,7 @@ describe('ServerRendererBrowser', () => {
7373
});
7474
const browser = new ServerRendererBrowser(
7575
ServerRendererConfigurationFactory.createConfiguration({
76-
browser: { suppressCodeGenerationFromStringsWarning: true }
76+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true }
7777
})
7878
);
7979
const results = await browser.render(MockedURLList.slice(0, 15).map((url) => ({ url })));
@@ -136,7 +136,7 @@ describe('ServerRendererBrowser', () => {
136136

137137
const browser = new ServerRendererBrowser(
138138
ServerRendererConfigurationFactory.createConfiguration({
139-
browser: { suppressCodeGenerationFromStringsWarning: true }
139+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true }
140140
})
141141
);
142142
const results = await browser.render(
@@ -200,7 +200,7 @@ describe('ServerRendererBrowser', () => {
200200
});
201201
const browser = new ServerRendererBrowser(
202202
ServerRendererConfigurationFactory.createConfiguration({
203-
browser: { suppressCodeGenerationFromStringsWarning: true }
203+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true }
204204
})
205205
);
206206
const results = await browser.render([{ url: 'https://example.com/gb/en/' }]);
@@ -233,7 +233,7 @@ describe('ServerRendererBrowser', () => {
233233

234234
const browser = new ServerRendererBrowser(
235235
ServerRendererConfigurationFactory.createConfiguration({
236-
browser: { suppressCodeGenerationFromStringsWarning: true },
236+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
237237
render: {
238238
timeout: 100
239239
}
@@ -273,7 +273,7 @@ The page may contain scripts with timer loops that prevent it from completing. Y
273273

274274
const browser = new ServerRendererBrowser(
275275
ServerRendererConfigurationFactory.createConfiguration({
276-
browser: { suppressCodeGenerationFromStringsWarning: true },
276+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
277277
render: {
278278
timeout: 100
279279
},
@@ -332,7 +332,7 @@ Timer #1
332332

333333
const browser = new ServerRendererBrowser(
334334
ServerRendererConfigurationFactory.createConfiguration({
335-
browser: { suppressCodeGenerationFromStringsWarning: true }
335+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true }
336336
})
337337
);
338338
const results = await browser.render([{ url: 'https://example.com/gb/en/' }]);
@@ -395,7 +395,7 @@ Timer #1
395395

396396
const browser = new ServerRendererBrowser(
397397
ServerRendererConfigurationFactory.createConfiguration({
398-
browser: { suppressCodeGenerationFromStringsWarning: true },
398+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
399399
render: {
400400
allShadowRoots: true
401401
}
@@ -466,7 +466,7 @@ Timer #1
466466

467467
const browser = new ServerRendererBrowser(
468468
ServerRendererConfigurationFactory.createConfiguration({
469-
browser: { suppressCodeGenerationFromStringsWarning: true },
469+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
470470
render: {
471471
serializableShadowRoots: true
472472
}
@@ -547,7 +547,7 @@ Timer #1
547547

548548
const browser = new ServerRendererBrowser(
549549
ServerRendererConfigurationFactory.createConfiguration({
550-
browser: { suppressCodeGenerationFromStringsWarning: true },
550+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
551551
render: {
552552
allShadowRoots: true,
553553
excludeShadowRootTags: ['custom-element']
@@ -616,7 +616,7 @@ Timer #1
616616
);
617617
const browser = new ServerRendererBrowser(
618618
ServerRendererConfigurationFactory.createConfiguration({
619-
browser: { suppressCodeGenerationFromStringsWarning: true },
619+
browser: { suppressInsecureJavaScriptEnvironmentWarning: true },
620620
cache: {
621621
disable: true
622622
}

packages/@happy-dom/server-renderer/test/utilities/MockedConfiguration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export default <IServerRendererConfiguration>{
66
browser: {
77
disableJavaScriptEvaluation: false,
88
enableJavaScriptEvaluation: false,
9-
suppressCodeGenerationFromStringsWarning: true,
9+
suppressCodeGenerationFromStringsWarning: false,
10+
suppressInsecureJavaScriptEnvironmentWarning: true,
1011
disableJavaScriptFileLoading: true,
1112
disableCSSFileLoading: true,
1213
disableIframePageLoading: false,

packages/@happy-dom/server-renderer/test/utilities/ProcessArgumentsParser.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,19 +325,19 @@ describe('ProcessArgumentsParser', () => {
325325
).toEqual(expectedConfig);
326326
});
327327

328-
it('Returns configuration with suppressed code generation warning.', async () => {
328+
it('Returns configuration with suppressed JavaScript environment warning.', async () => {
329329
const expectedConfig = {
330330
...DefaultServerRendererConfiguration,
331331
browser: {
332332
...DefaultServerRendererConfiguration.browser,
333-
suppressCodeGenerationFromStringsWarning: true
333+
suppressInsecureJavaScriptEnvironmentWarning: true
334334
}
335335
};
336336
expect(
337337
await ProcessArgumentsParser.getConfiguration([
338338
'node',
339339
'script.js',
340-
'--browser.suppressCodeGenerationFromStringsWarning'
340+
'--browser.suppressInsecureJavaScriptEnvironmentWarning'
341341
])
342342
).toEqual(expectedConfig);
343343
});
@@ -349,6 +349,7 @@ describe('ProcessArgumentsParser', () => {
349349
'browser.enableFileSystemHttpRequests',
350350
'browser.disableIframePageLoading',
351351
'browser.disableJavaScriptEvaluation',
352+
'browser.suppressCodeGenerationFromStringsWarning',
352353
// Special handling
353354
'browser.enableJavaScriptEvaluation',
354355
'browser.fetch.requestHeaders',

packages/happy-dom/src/PropertySymbol.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,6 @@ export const rulePrefix = Symbol('rulePrefix');
405405
export const virtualServerFile = Symbol('virtualServerFile');
406406
export const frames = Symbol('frames');
407407
export const disableEvaluation = Symbol('disableEvaluation');
408+
export const validateJavaScriptExecutionEnvironment = Symbol(
409+
'validateJavaScriptExecutionEnvironment'
410+
);

packages/happy-dom/src/browser/DefaultBrowserSettings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default <IBrowserSettings>{
1515
errorCapture: BrowserErrorCaptureEnum.tryAndCatch,
1616
enableFileSystemHttpRequests: false,
1717
suppressCodeGenerationFromStringsWarning: false,
18+
suppressInsecureJavaScriptEnvironmentWarning: false,
1819
timer: {
1920
maxTimeout: -1,
2021
maxIntervalTime: -1,

packages/happy-dom/src/browser/types/IBrowserSettings.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,16 @@ export default interface IBrowserSettings {
4040
/** Handle disabled resource loading as success */
4141
handleDisabledFileLoadingAsSuccess: boolean;
4242

43-
/** Suppresses the warning that is printed when code generation from strings is enabled at process level. */
43+
/**
44+
* Suppresses the warning that is printed when code generation from strings is enabled at process level.
45+
*
46+
* @deprecated Use "suppressInsecureJavaScriptEnvironmentWarning" instead.
47+
*/
4448
suppressCodeGenerationFromStringsWarning: boolean;
4549

50+
/** Suppresses the warning that is printed when the JavaScript execution environment is insecure. */
51+
suppressInsecureJavaScriptEnvironmentWarning: boolean;
52+
4653
/**
4754
* Settings for timers
4855
*/

packages/happy-dom/src/browser/types/IOptionalBrowserSettings.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,16 @@ export default interface IOptionalBrowserSettings {
3737
/** Handle disabled file loading as success */
3838
handleDisabledFileLoadingAsSuccess?: boolean;
3939

40-
/** Suppresses the warning that is printed when code generation from strings is enabled at process level. */
40+
/**
41+
* Suppresses the warning that is printed when code generation from strings is enabled at process level.
42+
*
43+
* @deprecated Use "suppressInsecureJavaScriptEnvironmentWarning" instead.
44+
*/
4145
suppressCodeGenerationFromStringsWarning?: boolean;
4246

47+
/** Suppresses the warning that is printed when the JavaScript execution environment is insecure. */
48+
suppressInsecureJavaScriptEnvironmentWarning?: boolean;
49+
4350
/** Settings for timers */
4451
timer?: {
4552
maxTimeout?: number;

packages/happy-dom/src/window/BrowserWindow.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ const TIMER = {
331331

332332
const IS_NODE_JS_TIMEOUT_ENVIRONMENT = setTimeout.toString().includes('new Timeout');
333333

334-
const IS_PROCESS_LEVEL_CODE_GENERATION_FROM_STRINGS_ALLOWED = (() => {
334+
const IS_CODE_GENERATION_FROM_STRINGS_ENVIRONMENT = (() => {
335335
try {
336336
// eslint-disable-next-line no-new-func
337337
new Function('return true;')();
@@ -341,6 +341,16 @@ const IS_PROCESS_LEVEL_CODE_GENERATION_FROM_STRINGS_ALLOWED = (() => {
341341
}
342342
})();
343343

344+
const IS_UNFROZEN_INTRINSICS_ENVIRONMENT = (() => {
345+
try {
346+
(<any>globalThis.Function)['$UNFROZEN$'] = true;
347+
delete (<any>globalThis.Function)['$UNFROZEN$'];
348+
return true;
349+
} catch {
350+
return false;
351+
}
352+
})();
353+
344354
/**
345355
* Class for PerformanceObserverEntryList as it is only available as an interface from Node.js.
346356
*/
@@ -858,23 +868,10 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
858868
constructor(browserFrame: IBrowserFrame, options?: { url?: string }) {
859869
super();
860870

861-
if (
862-
IS_PROCESS_LEVEL_CODE_GENERATION_FROM_STRINGS_ALLOWED &&
863-
browserFrame.page.context.browser.settings.enableJavaScriptEvaluation &&
864-
!browserFrame.page.context.browser.settings.suppressCodeGenerationFromStringsWarning
865-
) {
866-
// eslint-disable-next-line no-console
867-
console.warn(
868-
'\nWarning! Happy DOM has JavaScript evaluation enabled and is running in an environment with code generation from strings (eval) enabled at process level.' +
869-
'\n\nA VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks. The attacker can use code generation to escape the VM and run code at process level.' +
870-
'\n\nIt is recommended to disable code generation at process level by running node with the "--disallow-code-generation-from-strings" flag enabled when Javascript evaluation is enabled in Happy DOM.' +
871-
' You can suppress this warning by setting "suppressCodeGenerationFromStringsWarning" to "true" at your own risk.' +
872-
'\n\nFor more information, see https://github.com/capricorn86/happy-dom/wiki/Code-Generation-From-Strings-Warning\n\n'
873-
);
874-
}
875-
876871
this.#browserFrame = browserFrame;
877872

873+
this[PropertySymbol.validateJavaScriptExecutionEnvironment]();
874+
878875
this.console = browserFrame.page.console;
879876

880877
this[PropertySymbol.readyStateManager] = new DocumentReadyStateManager(browserFrame);
@@ -1857,6 +1854,28 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
18571854
}
18581855
}
18591856

1857+
/**
1858+
* Checks if the JavaScript execution environment is secure and outputs a warning if not.
1859+
*/
1860+
protected [PropertySymbol.validateJavaScriptExecutionEnvironment](): void {
1861+
const browserFrame = this.#browserFrame;
1862+
if (
1863+
(IS_CODE_GENERATION_FROM_STRINGS_ENVIRONMENT || IS_UNFROZEN_INTRINSICS_ENVIRONMENT) &&
1864+
browserFrame.page.context.browser.settings.enableJavaScriptEvaluation &&
1865+
!browserFrame.page.context.browser.settings.suppressInsecureJavaScriptEnvironmentWarning &&
1866+
!browserFrame.page.context.browser.settings.suppressCodeGenerationFromStringsWarning
1867+
) {
1868+
// eslint-disable-next-line no-console
1869+
console.warn(
1870+
'\nWarning! Happy DOM has JavaScript evaluation enabled and is running in an insecure environment.' +
1871+
'\n\nA VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks. The attacker can escape the VM and run code at process level.' +
1872+
'\n\nIt is recommended to disable code generation and freeze all builtins at process level by running node with the flags "--disallow-code-generation-from-strings" and "--frozen-intrinsics".' +
1873+
' You can suppress this warning by setting "suppressInsecureJavaScriptEnvironmentWarning" to "true" at your own risk.' +
1874+
'\n\nFor more information, see https://github.com/capricorn86/happy-dom/wiki/JavaScript-Evaluation-Warning\n\n'
1875+
);
1876+
}
1877+
}
1878+
18601879
/**
18611880
* Destroys the window.
18621881
*/

0 commit comments

Comments
 (0)