Skip to content

Commit

Permalink
feat: disable exec
Browse files Browse the repository at this point in the history
  • Loading branch information
weichengpan committed Mar 4, 2024
1 parent 1be56aa commit d2113b1
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 49 deletions.
17 changes: 8 additions & 9 deletions api/src/operations/exec/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { VMError } from 'vm2';
import { test, expect } from 'vitest';

import config from './index.js';
Expand All @@ -15,7 +14,7 @@ test('Rejects when modules are used without modules being allowed', async () =>
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new VMError("Cannot find module 'test'"));
).rejects.toEqual(new Error('permission denied'));
});

test('Rejects when code contains syntax errors', async () => {
Expand All @@ -30,7 +29,7 @@ test('Rejects when code contains syntax errors', async () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new SyntaxError('Unexpected end of input'));
).rejects.toEqual(new Error('permission denied'));
});

test('Rejects when returned function does something illegal', async () => {
Expand All @@ -47,7 +46,7 @@ test('Rejects when returned function does something illegal', async () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new ReferenceError('a is not defined'));
).rejects.toEqual(new Error('permission denied'));
});

test("Rejects when code doesn't return valid function", async () => {
Expand All @@ -62,7 +61,7 @@ test("Rejects when code doesn't return valid function", async () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new TypeError('fn is not a function'));
).rejects.toEqual(new Error('permission denied'));
});

test('Rejects returned function throws errors', async () => {
Expand All @@ -79,7 +78,7 @@ test('Rejects returned function throws errors', async () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new Error('test'));
).rejects.toEqual(new Error('permission denied'));
});

test('Executes function when valid', () => {
Expand All @@ -98,7 +97,7 @@ test('Executes function when valid', () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).resolves.toEqual({ result: 'start test' });
).rejects.toEqual(new Error('permission denied'));
});

test('Allows built-in modules that are whitelisted', () => {
Expand All @@ -119,7 +118,7 @@ test('Allows built-in modules that are whitelisted', () => {
FLOWS_EXEC_ALLOWED_MODULES: 'crypto',
},
} as any)
).resolves.toEqual({ result: '943e891bf6042f2db8926493c0f94e45b72cb58a21145fdfa3c23b5c057e4b2d' });
).rejects.toEqual(new Error('permission denied'));
});

test('Allows external modules that are whitelisted', () => {
Expand All @@ -138,5 +137,5 @@ test('Allows external modules that are whitelisted', () => {
FLOWS_EXEC_ALLOWED_MODULES: 'bytes',
},
} as any)
).resolves.toEqual({ result: '1000B' });
).rejects.toEqual(new Error('permission denied'));
});
43 changes: 3 additions & 40 deletions api/src/operations/exec/index.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,12 @@
import { defineOperationApi, toArray } from '@smartnews/directus-utils';
import { isBuiltin } from 'node:module';
import type { NodeVMOptions } from 'vm2';
import { NodeVM, VMScript } from 'vm2';
import { defineOperationApi } from '@smartnews/directus-utils';

type Options = {
code: string;
};

export default defineOperationApi<Options>({
id: 'exec',
handler: async ({ code }, { data, env }) => {
const allowedModules = env['FLOWS_EXEC_ALLOWED_MODULES'] ? toArray(env['FLOWS_EXEC_ALLOWED_MODULES']) : [];
const allowedModulesBuiltIn: string[] = [];
const allowedModulesExternal: string[] = [];
const allowedEnv = data['$env'] ?? {};

const opts: NodeVMOptions = {
eval: false,
wasm: false,
env: allowedEnv,
};

for (const module of allowedModules) {
if (isBuiltin(module)) {
allowedModulesBuiltIn.push(module);
} else {
allowedModulesExternal.push(module);
}
}

if (allowedModules.length > 0) {
opts.require = {
builtin: allowedModulesBuiltIn,
external: {
modules: allowedModulesExternal,
transitive: false,
},
};
}

const vm = new NodeVM(opts);

const script = new VMScript(code).compile();
const fn = await vm.run(script);

return await fn(data);
handler: async () => {
throw new Error('permission denied');
},
});

0 comments on commit d2113b1

Please sign in to comment.