|
|
|
|
@@ -30,15 +30,15 @@ import { type Context, createContext, runInContext } from 'node:vm';
|
|
|
|
|
|
|
|
|
|
import type { MainConfig } from '@/config/main-config';
|
|
|
|
|
import { UnsupportedFunctionError } from '@/js-task-runner/errors/unsupported-function.error';
|
|
|
|
|
import { EXPOSED_RPC_METHODS, UNSUPPORTED_HELPER_FUNCTIONS } from '@/runner-types';
|
|
|
|
|
import type {
|
|
|
|
|
DataRequestResponse,
|
|
|
|
|
InputDataChunkDefinition,
|
|
|
|
|
PartialAdditionalData,
|
|
|
|
|
TaskResultData,
|
|
|
|
|
} from '@/runner-types';
|
|
|
|
|
import type { TaskParams } from '@/task-runner';
|
|
|
|
|
import { EXPOSED_RPC_METHODS, UNSUPPORTED_HELPER_FUNCTIONS } from '@/runner-types';
|
|
|
|
|
import { noOp, TaskRunner } from '@/task-runner';
|
|
|
|
|
import type { TaskParams } from '@/task-runner';
|
|
|
|
|
|
|
|
|
|
import { BuiltInsParser } from './built-ins-parser/built-ins-parser';
|
|
|
|
|
import { BuiltInsParserState } from './built-ins-parser/built-ins-parser-state';
|
|
|
|
|
@@ -95,6 +95,8 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
|
|
|
|
|
private readonly taskDataReconstruct = new DataRequestResponseReconstruct();
|
|
|
|
|
|
|
|
|
|
private readonly mode: 'secure' | 'insecure' = 'secure';
|
|
|
|
|
|
|
|
|
|
constructor(config: MainConfig, name = 'JS Task Runner') {
|
|
|
|
|
super({
|
|
|
|
|
taskType: 'javascript',
|
|
|
|
|
@@ -117,19 +119,17 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
const allowedExternalModules = parseModuleAllowList(
|
|
|
|
|
jsRunnerConfig.allowedExternalModules ?? '',
|
|
|
|
|
);
|
|
|
|
|
this.mode = jsRunnerConfig.insecureMode ? 'insecure' : 'secure';
|
|
|
|
|
|
|
|
|
|
this.requireResolver = createRequireResolver({
|
|
|
|
|
allowedBuiltInModules,
|
|
|
|
|
allowedExternalModules,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.preventPrototypePollution(allowedExternalModules, jsRunnerConfig.allowPrototypeMutation);
|
|
|
|
|
if (this.mode === 'secure') this.preventPrototypePollution(allowedExternalModules);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private preventPrototypePollution(
|
|
|
|
|
allowedExternalModules: Set<string> | '*',
|
|
|
|
|
allowPrototypeMutation: boolean,
|
|
|
|
|
) {
|
|
|
|
|
private preventPrototypePollution(allowedExternalModules: Set<string> | '*') {
|
|
|
|
|
if (allowedExternalModules instanceof Set) {
|
|
|
|
|
// This is a workaround to enable the allowed external libraries to mutate
|
|
|
|
|
// prototypes directly. For example momentjs overrides .toString() directly
|
|
|
|
|
@@ -141,11 +141,11 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Freeze globals if needed
|
|
|
|
|
if (!allowPrototypeMutation) {
|
|
|
|
|
// Freeze globals, except in tests because Jest needs to be able to mutate prototypes
|
|
|
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
|
|
|
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
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
|
|
|
.map((name) => globalThis[name])
|
|
|
|
|
.filter((value) => typeof value === 'function')
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
|
|
|
@@ -256,9 +256,15 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
|
|
|
|
|
signal.addEventListener('abort', abortHandler, { once: true });
|
|
|
|
|
|
|
|
|
|
const taskResult = runInContext(this.createVmExecutableCode(settings.code), context, {
|
|
|
|
|
timeout: this.taskTimeout * 1000,
|
|
|
|
|
}) as Promise<TaskResultData['result']>;
|
|
|
|
|
let taskResult: Promise<TaskResultData['result']>;
|
|
|
|
|
|
|
|
|
|
if (this.mode === 'secure') {
|
|
|
|
|
taskResult = runInContext(this.createVmExecutableCode(settings.code), context, {
|
|
|
|
|
timeout: this.taskTimeout * 1000,
|
|
|
|
|
}) as Promise<TaskResultData['result']>;
|
|
|
|
|
} else {
|
|
|
|
|
taskResult = this.runDirectly<TaskResultData['result']>(settings.code, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void taskResult
|
|
|
|
|
.then(resolve)
|
|
|
|
|
@@ -319,9 +325,15 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
|
|
|
|
|
signal.addEventListener('abort', abortHandler);
|
|
|
|
|
|
|
|
|
|
const taskResult = runInContext(this.createVmExecutableCode(settings.code), context, {
|
|
|
|
|
timeout: this.taskTimeout * 1000,
|
|
|
|
|
}) as Promise<INodeExecutionData>;
|
|
|
|
|
let taskResult: Promise<INodeExecutionData>;
|
|
|
|
|
|
|
|
|
|
if (this.mode === 'secure') {
|
|
|
|
|
taskResult = runInContext(this.createVmExecutableCode(settings.code), context, {
|
|
|
|
|
timeout: this.taskTimeout * 1000,
|
|
|
|
|
}) as Promise<INodeExecutionData>;
|
|
|
|
|
} else {
|
|
|
|
|
taskResult = this.runDirectly<INodeExecutionData>(settings.code, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void taskResult
|
|
|
|
|
.then(resolve)
|
|
|
|
|
@@ -551,4 +563,14 @@ export class JsTaskRunner extends TaskRunner {
|
|
|
|
|
`module.exports = async function VmCodeWrapper() {${code}\n}()`,
|
|
|
|
|
].join('; ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async runDirectly<T>(code: string, context: Context): Promise<T> {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
|
|
|
const fn = new Function(
|
|
|
|
|
'context',
|
|
|
|
|
`with(context) { return (async function() {${code}\n})(); }`,
|
|
|
|
|
);
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
|
|
|
|
|
return await fn(context);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|