diff --git a/package.json b/package.json index f0e63f73..e67b3217 100644 --- a/package.json +++ b/package.json @@ -38,5 +38,8 @@ "rimraf": "^3.0.2", "ts-jest": "^26.1.0", "typescript": "^3.9.2" + }, + "dependencies": { + "vm2": "^3.9.2" } } diff --git a/src/client/interpreter/Sandbox.test.ts b/src/client/interpreter/Sandbox.test.ts new file mode 100644 index 00000000..9db41f20 --- /dev/null +++ b/src/client/interpreter/Sandbox.test.ts @@ -0,0 +1,60 @@ +import { Sandbox } from "./Sandbox"; + +describe("sandbox", () => { + let sandbox!: Sandbox; + + beforeEach(() => { + process.env.SECRET = "MuchSecret"; + sandbox = new Sandbox(); + }); + + it("prevents string masking attackt", () => { + const js = ` + // Let x be any value not in + // (null, undefined, Object.create(null)). + var x = {}, + // If the attacker can control three strings + a = "constructor", + b = "constructor", + s = "process.env.SECRET = 'overwrite'"; + // and trick code into doing two property lookups + // they control, a call with a string they control, + // and one more call with any argument + x[a][b](s)(); + // then they can cause any side-effect achievable + // solely via objects reachable from the global scope. + // This includes full access to any exported module APIs, + // all declarations in the current module, and access + // to builtin modules like child_process, fs, and net. + `; + + expect(() => sandbox.evalJS(js)).toThrowError(); + expect(process.env.SECRET).toEqual("MuchSecret"); + }); + + it("prevents primodial types pollution", () => { + const js = ` + Array.prototype.toString = () => { + console.log("I have been called!!!!"); + return "Modified!!!!"; + };`; + sandbox.evalJS(js); + expect([1, 2, 3].toString()).toEqual("1,2,3"); + }); + + it("prevents quitting the process", () => { + const js = `process.exit();`; + expect(() => sandbox.evalJS(js)).toThrowError(); + }); + + describe("Halting problem (Stalling the event loop)", () => { + it("while(true)", () => { + expect(() => sandbox.evalJS(`while(true){1+1}`)).toThrowError(/Script execution timed out/); + + }); + + it("prevents using async", () => { + expect(() => sandbox.evalJS(`new Promise.resolve(5)`)).toThrowError(/Promise/); + }); + }); +}); diff --git a/src/client/interpreter/Sandbox.ts b/src/client/interpreter/Sandbox.ts new file mode 100644 index 00000000..c9391ee3 --- /dev/null +++ b/src/client/interpreter/Sandbox.ts @@ -0,0 +1,14 @@ +import { VM } from "vm2"; + +export class Sandbox { + evalJS = (js: string): unknown => { + const vm = new VM({ + sandbox: {}, + wasm: false, + eval: false, + timeout: 100, + }); + + return vm.run(`'use strict'; ${js}`); + }; +} diff --git a/yarn.lock b/yarn.lock index e2ba70b9..7dce95d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6571,6 +6571,11 @@ vlq@^0.2.2: resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== +vm2@^3.9.2: + version "3.9.2" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.2.tgz#a4085d2d88a808a1b3c06d5478c2db3222a9cc30" + integrity sha512-nzyFmHdy2FMg7mYraRytc2jr4QBaUY3TEGe3q3bK8EgS9WC98wxn2jrPxS/ruWm+JGzrEIIeufKweQzVoQEd+Q== + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"