feat(core): Make runtime prototype mutation protection configurable for task runner (#14515)

This commit is contained in:
Iván Ovejero
2025-04-10 14:12:25 +02:00
committed by GitHub
parent 52170f1bbc
commit d6ae3889ca
3 changed files with 47 additions and 4 deletions

View File

@@ -7,4 +7,14 @@ export class JsRunnerConfig {
@Env('NODE_FUNCTION_ALLOW_EXTERNAL')
allowedExternalModules: string = '';
/**
* Whether to allow prototype mutation for external libraries. Set to `false`
* to allow modules that rely on runtime prototype mutation, e.g. `puppeteer`,
* at the cost of security.
*
* @default false
*/
@Env('N8N_RUNNERS_ALLOW_PROTOTYPE_MUTATION')
allowPrototypeMutation: boolean = false;
}

View File

@@ -30,6 +30,11 @@ import {
jest.mock('ws');
const defaultConfig = new MainConfig();
defaultConfig.jsRunnerConfig ??= {
allowedBuiltInModules: '',
allowedExternalModules: '',
allowPrototypeMutation: true, // needed for jest
};
describe('JsTaskRunner', () => {
const createRunnerWithOpts = (
@@ -1435,6 +1440,31 @@ describe('JsTaskRunner', () => {
// @ts-expect-error Non-existing property
expect(Duration.fromObject({ hours: 1 }).maliciousKey).toBeUndefined();
});
it('should allow prototype mutation when `allowPrototypeMutation` is true', async () => {
const runner = createRunnerWithOpts({
allowPrototypeMutation: true,
});
const outcome = await executeForAllItems({
code: `
const obj = {};
Object.prototype.maliciousProperty = 'compromised';
return [{ json: {
prototypeMutated: obj.maliciousProperty === 'compromised'
}}];
`,
inputItems: [{ a: 1 }],
runner,
});
expect(outcome.result).toEqual([wrapIntoJson({ prototypeMutated: true })]);
// @ts-expect-error Non-existing property
delete Object.prototype.maliciousProperty;
});
});
describe('stack trace', () => {

View File

@@ -117,10 +117,13 @@ export class JsTaskRunner extends TaskRunner {
allowedExternalModules,
});
this.preventPrototypePollution(allowedExternalModules);
this.preventPrototypePollution(allowedExternalModules, jsRunnerConfig.allowPrototypeMutation);
}
private preventPrototypePollution(allowedExternalModules: Set<string> | '*') {
private preventPrototypePollution(
allowedExternalModules: Set<string> | '*',
allowPrototypeMutation: boolean,
) {
if (allowedExternalModules instanceof Set) {
// This is a workaround to enable the allowed external libraries to mutate
// prototypes directly. For example momentjs overrides .toString() directly
@@ -132,8 +135,8 @@ export class JsTaskRunner extends TaskRunner {
}
}
// Freeze globals, except for Jest
if (process.env.NODE_ENV !== 'test') {
// Freeze globals if needed
if (!allowPrototypeMutation) {
Object.getOwnPropertyNames(globalThis)
// @ts-expect-error globalThis does not have string in index signature
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access