diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 2d8d7ce8e2..2d6aa4e14f 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -39,6 +39,18 @@ tmpl.tmpl.errorHandler = (error: Error) => { } }; +// eslint-disable-next-line @typescript-eslint/naming-convention +const AsyncFunction = (async () => {}).constructor as FunctionConstructor; + +const fnConstructors = { + sync: Function.prototype.constructor, + // eslint-disable-next-line @typescript-eslint/ban-types + async: AsyncFunction.prototype.constructor, + mock: () => { + throw new ExpressionError('Arbitrary code execution detected'); + }, +}; + export class Expression { workflow: Workflow; @@ -315,6 +327,9 @@ export class Expression { data: IWorkflowDataProxyData, ): tmpl.ReturnValue | undefined { try { + [Function, AsyncFunction].forEach(({ prototype }) => + Object.defineProperty(prototype, 'constructor', { value: fnConstructors.mock }), + ); return tmpl.tmpl(expression, data); } catch (error) { if (error instanceof ExpressionError) { @@ -352,6 +367,11 @@ export class Expression { throw new Error(match.groups.msg); } + } finally { + Object.defineProperty(Function.prototype, 'constructor', { value: fnConstructors.sync }); + Object.defineProperty(AsyncFunction.prototype, 'constructor', { + value: fnConstructors.async, + }); } return null; } diff --git a/packages/workflow/test/Expression.test.ts b/packages/workflow/test/Expression.test.ts index d2ef93e07a..5229361440 100644 --- a/packages/workflow/test/Expression.test.ts +++ b/packages/workflow/test/Expression.test.ts @@ -150,6 +150,13 @@ describe('Expression', () => { expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1)); expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString()); }); + + it('should not able to do arbitrary code execution', () => { + const testFn = jest.fn(); + Object.assign(global, { testFn }); + evaluate("={{ Date['constructor']('testFn()')()}}"); + expect(testFn).not.toHaveBeenCalled(); + }); }); describe('Test all expression value fixtures', () => {