diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index cddab3c9b4..04b323cf80 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -47,6 +47,13 @@ describe("jsRunner", () => { expect(output).toBe(3) }) + it("should prevent sandbox escape", async () => { + const output = await processJS( + `return this.constructor.constructor("return process")()` + ) + expect(output).toBe("Error while executing JS") + }) + describe("helpers", () => { runJsHelpersTests({ funcWrap: (func: any) => config.doInContext(config.getAppId(), func), diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index a6179234de..ceafd5364f 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -2,13 +2,13 @@ "name": "@budibase/string-templates", "version": "0.0.0", "description": "Handlebars wrapper for Budibase templating.", - "main": "src/index.cjs", + "main": "src/index.js", "module": "dist/bundle.mjs", "license": "MPL-2.0", "types": "dist/index.d.ts", "exports": { ".": { - "require": "./src/index.cjs", + "require": "./src/index.js", "import": "./dist/bundle.mjs" }, "./package.json": "./package.json", @@ -29,8 +29,7 @@ "@budibase/handlebars-helpers": "^0.13.1", "dayjs": "^1.10.8", "handlebars": "^4.7.6", - "lodash.clonedeep": "^4.5.0", - "vm2": "^3.9.19" + "lodash.clonedeep": "^4.5.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^17.1.0", diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 61a9ed21f1..99d7df10f7 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -4,7 +4,7 @@ const { LITERAL_MARKER } = require("../helpers/constants") const { getJsHelperList } = require("./list") // The method of executing JS scripts depends on the bundle being built. -// This setter is used in the entrypoint (either index.cjs or index.mjs). +// This setter is used in the entrypoint (either index.js or index.mjs). let runJS module.exports.setJSRunner = runner => (runJS = runner) diff --git a/packages/string-templates/src/index.cjs b/packages/string-templates/src/index.cjs deleted file mode 100644 index 68fdfe864e..0000000000 --- a/packages/string-templates/src/index.cjs +++ /dev/null @@ -1,43 +0,0 @@ -const templates = require("./index.js") - -/** - * CJS entrypoint for rollup - */ -module.exports.isValid = templates.isValid -module.exports.makePropSafe = templates.makePropSafe -module.exports.getManifest = templates.getManifest -module.exports.isJSBinding = templates.isJSBinding -module.exports.encodeJSBinding = templates.encodeJSBinding -module.exports.decodeJSBinding = templates.decodeJSBinding -module.exports.processStringSync = templates.processStringSync -module.exports.processObjectSync = templates.processObjectSync -module.exports.processString = templates.processString -module.exports.processObject = templates.processObject -module.exports.doesContainStrings = templates.doesContainStrings -module.exports.doesContainString = templates.doesContainString -module.exports.disableEscaping = templates.disableEscaping -module.exports.findHBSBlocks = templates.findHBSBlocks -module.exports.convertToJS = templates.convertToJS -module.exports.setJSRunner = templates.setJSRunner -module.exports.FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX -module.exports.helpersToRemoveForJs = templates.helpersToRemoveForJs - -if (!process.env.NO_JS) { - const { VM } = require("vm2") - const { setJSRunner } = require("./helpers/javascript") - /** - * Use vm2 to run JS scripts in a node env - */ - setJSRunner((js, context) => { - const vm = new VM({ - sandbox: context, - timeout: 1000, - }) - return vm.run(js) - }) -} - -const errors = require("./errors") -for (const error in errors) { - module.exports[error] = errors[error] -} diff --git a/packages/string-templates/test/basic.spec.js b/packages/string-templates/test/basic.spec.js index a5054bf489..0b78e3cafd 100644 --- a/packages/string-templates/test/basic.spec.js +++ b/packages/string-templates/test/basic.spec.js @@ -8,7 +8,7 @@ const { doesContainString, disableEscaping, findHBSBlocks, -} = require("../src/index.cjs") +} = require("../src/index.js") describe("Test that the string processing works correctly", () => { it("should process a basic template string", async () => { diff --git a/packages/string-templates/test/escapes.spec.js b/packages/string-templates/test/escapes.spec.js index d27ca64240..caa2f7d0c1 100644 --- a/packages/string-templates/test/escapes.spec.js +++ b/packages/string-templates/test/escapes.spec.js @@ -1,4 +1,4 @@ -const { processString } = require("../src/index.cjs") +const { processString } = require("../src/index.js") describe("Handling context properties with spaces in their name", () => { it("should allow through literal specifiers", async () => { diff --git a/packages/string-templates/test/hbsToJs.spec.js b/packages/string-templates/test/hbsToJs.spec.js index 5d5b6c6d0b..08d8ff5f67 100644 --- a/packages/string-templates/test/hbsToJs.spec.js +++ b/packages/string-templates/test/hbsToJs.spec.js @@ -1,4 +1,4 @@ -const { convertToJS } = require("../src/index.cjs") +const { convertToJS } = require("../src/index.js") function checkLines(response, lines) { const toCheck = response.split("\n") diff --git a/packages/string-templates/test/helpers.spec.js b/packages/string-templates/test/helpers.spec.js index 49ee0acbda..86fef538d3 100644 --- a/packages/string-templates/test/helpers.spec.js +++ b/packages/string-templates/test/helpers.spec.js @@ -1,4 +1,4 @@ -const { processString, processObject, isValid } = require("../src/index.cjs") +const { processString, processObject, isValid } = require("../src/index.js") const tableJson = require("./examples/table.json") const dayjs = require("dayjs") const { UUID_REGEX } = require("./constants") diff --git a/packages/string-templates/test/javascript.spec.js b/packages/string-templates/test/javascript.spec.js index d8b42678a7..0e9f196da6 100644 --- a/packages/string-templates/test/javascript.spec.js +++ b/packages/string-templates/test/javascript.spec.js @@ -1,149 +1,156 @@ -const { processStringSync, encodeJSBinding } = require("../src/index.cjs") +const vm = require("vm") + +const { + processStringSync, + encodeJSBinding, + setJSRunner, +} = require("../src/index.js") const { UUID_REGEX } = require("./constants") const processJS = (js, context) => { return processStringSync(encodeJSBinding(js), context) } -describe("Test the JavaScript helper", () => { - it("should execute a simple expression", () => { - const output = processJS(`return 1 + 2`) - expect(output).toBe(3) - }) - - it("should be able to use primitive bindings", () => { - const output = processJS(`return $("foo")`, { - foo: "bar", +describe("Javascript", () => { + beforeAll(() => { + setJSRunner((js, context) => { + return vm.runInNewContext(js, context, { timeout: 1000 }) }) - expect(output).toBe("bar") }) - it("should be able to use an object binding", () => { - const output = processJS(`return $("foo").bar`, { - foo: { - bar: "baz", - }, + describe("Test the JavaScript helper", () => { + it("should execute a simple expression", () => { + const output = processJS(`return 1 + 2`) + expect(output).toBe(3) }) - expect(output).toBe("baz") - }) - it("should be able to use a complex object binding", () => { - const output = processJS(`return $("foo").bar[0].baz`, { - foo: { - bar: [ - { - baz: "shazbat", - }, - ], - }, + it("should be able to use primitive bindings", () => { + const output = processJS(`return $("foo")`, { + foo: "bar", + }) + expect(output).toBe("bar") }) - expect(output).toBe("shazbat") - }) - it("should be able to use a deep binding", () => { - const output = processJS(`return $("foo.bar.baz")`, { - foo: { - bar: { - baz: "shazbat", - }, - }, - }) - expect(output).toBe("shazbat") - }) - - it("should be able to return an object", () => { - const output = processJS(`return $("foo")`, { - foo: { - bar: { - baz: "shazbat", - }, - }, - }) - expect(output.bar.baz).toBe("shazbat") - }) - - it("should be able to return an array", () => { - const output = processJS(`return $("foo")`, { - foo: ["a", "b", "c"], - }) - expect(output[2]).toBe("c") - }) - - it("should be able to return null", () => { - const output = processJS(`return $("foo")`, { - foo: null, - }) - expect(output).toBe(null) - }) - - it("should be able to return undefined", () => { - const output = processJS(`return $("foo")`, { - foo: undefined, - }) - expect(output).toBe(undefined) - }) - - it("should be able to return 0", () => { - const output = processJS(`return $("foo")`, { - foo: 0, - }) - expect(output).toBe(0) - }) - - it("should be able to return an empty string", () => { - const output = processJS(`return $("foo")`, { - foo: "", - }) - expect(output).toBe("") - }) - - it("should be able to use a deep array binding", () => { - const output = processJS(`return $("foo.0.bar")`, { - foo: [ - { + it("should be able to use an object binding", () => { + const output = processJS(`return $("foo").bar`, { + foo: { bar: "baz", }, - ], + }) + expect(output).toBe("baz") + }) + + it("should be able to use a complex object binding", () => { + const output = processJS(`return $("foo").bar[0].baz`, { + foo: { + bar: [ + { + baz: "shazbat", + }, + ], + }, + }) + expect(output).toBe("shazbat") + }) + + it("should be able to use a deep binding", () => { + const output = processJS(`return $("foo.bar.baz")`, { + foo: { + bar: { + baz: "shazbat", + }, + }, + }) + expect(output).toBe("shazbat") + }) + + it("should be able to return an object", () => { + const output = processJS(`return $("foo")`, { + foo: { + bar: { + baz: "shazbat", + }, + }, + }) + expect(output.bar.baz).toBe("shazbat") + }) + + it("should be able to return an array", () => { + const output = processJS(`return $("foo")`, { + foo: ["a", "b", "c"], + }) + expect(output[2]).toBe("c") + }) + + it("should be able to return null", () => { + const output = processJS(`return $("foo")`, { + foo: null, + }) + expect(output).toBe(null) + }) + + it("should be able to return undefined", () => { + const output = processJS(`return $("foo")`, { + foo: undefined, + }) + expect(output).toBe(undefined) + }) + + it("should be able to return 0", () => { + const output = processJS(`return $("foo")`, { + foo: 0, + }) + expect(output).toBe(0) + }) + + it("should be able to return an empty string", () => { + const output = processJS(`return $("foo")`, { + foo: "", + }) + expect(output).toBe("") + }) + + it("should be able to use a deep array binding", () => { + const output = processJS(`return $("foo.0.bar")`, { + foo: [ + { + bar: "baz", + }, + ], + }) + expect(output).toBe("baz") + }) + + it("should handle errors", () => { + const output = processJS(`throw "Error"`) + expect(output).toBe("Error while executing JS") + }) + + it("should timeout after one second", () => { + const output = processJS(`while (true) {}`) + expect(output).toBe("Timed out while executing JS") + }) + + it("should prevent access to the process global", () => { + const output = processJS(`return process`) + expect(output).toBe("Error while executing JS") }) - expect(output).toBe("baz") }) - it("should handle errors", () => { - const output = processJS(`throw "Error"`) - expect(output).toBe("Error while executing JS") - }) + describe("check JS helpers", () => { + it("should error if using the format helper. not helpers.", () => { + const output = processJS(`return helper.toInt(4.3)`) + expect(output).toBe("Error while executing JS") + }) - it("should timeout after one second", () => { - const output = processJS(`while (true) {}`) - expect(output).toBe("Timed out while executing JS") - }) + it("should be able to use toInt", () => { + const output = processJS(`return helpers.toInt(4.3)`) + expect(output).toBe(4) + }) - it("should prevent access to the process global", () => { - const output = processJS(`return process`) - expect(output).toBe("Error while executing JS") - }) - - it("should prevent sandbox escape", () => { - const output = processJS( - `return this.constructor.constructor("return process")()` - ) - expect(output).toBe("Error while executing JS") - }) -}) - -describe("check JS helpers", () => { - it("should error if using the format helper. not helpers.", () => { - const output = processJS(`return helper.toInt(4.3)`) - expect(output).toBe("Error while executing JS") - }) - - it("should be able to use toInt", () => { - const output = processJS(`return helpers.toInt(4.3)`) - expect(output).toBe(4) - }) - - it("should be able to use uuid", () => { - const output = processJS(`return helpers.uuid()`) - expect(output).toMatch(UUID_REGEX) + it("should be able to use uuid", () => { + const output = processJS(`return helpers.uuid()`) + expect(output).toMatch(UUID_REGEX) + }) }) }) diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index fa8683960a..81183f13c9 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -1,3 +1,5 @@ +const vm = require("vm") + jest.mock("@budibase/handlebars-helpers/lib/math", () => { const actual = jest.requireActual("@budibase/handlebars-helpers/lib/math") @@ -15,7 +17,7 @@ jest.mock("@budibase/handlebars-helpers/lib/uuid", () => { } }) -const { processString } = require("../src/index.cjs") +const { processString, setJSRunner } = require("../src/index.js") const tk = require("timekeeper") const { getParsedManifest, runJsHelpersTests } = require("./utils") @@ -29,6 +31,12 @@ function escapeRegExp(string) { describe("manifest", () => { const manifest = getParsedManifest() + beforeAll(() => { + setJSRunner((js, context) => { + return vm.runInNewContext(js, context, { timeout: 1000 }) + }) + }) + describe("examples are valid", () => { describe.each(Object.keys(manifest))("%s", collection => { it.each(manifest[collection])("%s", async (_, { hbs, js }) => { diff --git a/packages/string-templates/test/renderApp.spec.js b/packages/string-templates/test/renderApp.spec.js index 13d478980d..582e70701f 100644 --- a/packages/string-templates/test/renderApp.spec.js +++ b/packages/string-templates/test/renderApp.spec.js @@ -1,4 +1,4 @@ -const { processString } = require("../src/index.cjs") +const { processString } = require("../src/index.js") describe("specific test case for whether or not full app template can still be rendered", () => { it("should be able to render the app template", async () => { diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 3a54502a3d..927a6e3aeb 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -5,7 +5,7 @@ const { convertToJS, processStringSync, encodeJSBinding, -} = require("../src/index.cjs") +} = require("../src/index.js") function tryParseJson(str) { if (typeof str !== "string") { diff --git a/yarn.lock b/yarn.lock index 33ee0f97bb..82d7b27359 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6403,7 +6403,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.1.0, acorn@^8.10.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.1.0, acorn@^8.10.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -21270,14 +21270,6 @@ vlq@^0.2.2: resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== -vm2@^3.9.19: - version "3.9.19" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.19.tgz#be1e1d7a106122c6c492b4d51c2e8b93d3ed6a4a" - integrity sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg== - dependencies: - acorn "^8.7.0" - acorn-walk "^8.2.0" - vuvuzela@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b"