Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove vm2 from stringtemplates #12994

Merged
merged 7 commits into from Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/server/src/jsRunner/tests/jsRunner.spec.ts
Expand Up @@ -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),
Expand Down
7 changes: 3 additions & 4 deletions packages/string-templates/package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/string-templates/src/helpers/javascript.js
Expand Up @@ -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)

Expand Down
43 changes: 0 additions & 43 deletions packages/string-templates/src/index.cjs

This file was deleted.

2 changes: 1 addition & 1 deletion packages/string-templates/test/basic.spec.js
Expand Up @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion 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 () => {
Expand Down
2 changes: 1 addition & 1 deletion 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")
Expand Down
2 changes: 1 addition & 1 deletion 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")
Expand Down
223 changes: 115 additions & 108 deletions 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)
describe("Javascript", () => {
beforeAll(() => {
setJSRunner((js, context) => {
return vm.runInNewContext(js, context, { timeout: 1000 })
})
})

it("should be able to use primitive bindings", () => {
const output = processJS(`return $("foo")`, {
foo: "bar",
describe("Test the JavaScript helper", () => {
it("should execute a simple expression", () => {
const output = processJS(`return 1 + 2`)
expect(output).toBe(3)
})
expect(output).toBe("bar")
})

it("should be able to use an object binding", () => {
const output = processJS(`return $("foo").bar`, {
foo: {
bar: "baz",
},
it("should be able to use primitive bindings", () => {
const output = processJS(`return $("foo")`, {
foo: "bar",
})
expect(output).toBe("bar")
})
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 an object binding", () => {
const output = processJS(`return $("foo").bar`, {
foo: {
bar: "baz",
},
})
expect(output).toBe("baz")
})
expect(output).toBe("shazbat")
})

it("should be able to use a deep binding", () => {
const output = processJS(`return $("foo.bar.baz")`, {
foo: {
bar: {
baz: "shazbat",
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")
})
expect(output).toBe("shazbat")
})

it("should be able to return an object", () => {
const output = processJS(`return $("foo")`, {
foo: {
bar: {
baz: "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")
})
expect(output.bar.baz).toBe("shazbat")
})

it("should be able to return an array", () => {
const output = processJS(`return $("foo")`, {
foo: ["a", "b", "c"],
it("should be able to return an object", () => {
const output = processJS(`return $("foo")`, {
foo: {
bar: {
baz: "shazbat",
},
},
})
expect(output.bar.baz).toBe("shazbat")
})
expect(output[2]).toBe("c")
})

it("should be able to return null", () => {
const output = processJS(`return $("foo")`, {
foo: null,
it("should be able to return an array", () => {
const output = processJS(`return $("foo")`, {
foo: ["a", "b", "c"],
})
expect(output[2]).toBe("c")
})
expect(output).toBe(null)
})

it("should be able to return undefined", () => {
const output = processJS(`return $("foo")`, {
foo: undefined,
it("should be able to return null", () => {
const output = processJS(`return $("foo")`, {
foo: null,
})
expect(output).toBe(null)
})
expect(output).toBe(undefined)
})

it("should be able to return 0", () => {
const output = processJS(`return $("foo")`, {
foo: 0,
it("should be able to return undefined", () => {
const output = processJS(`return $("foo")`, {
foo: undefined,
})
expect(output).toBe(undefined)
})
expect(output).toBe(0)
})

it("should be able to return an empty string", () => {
const output = processJS(`return $("foo")`, {
foo: "",
it("should be able to return 0", () => {
const output = processJS(`return $("foo")`, {
foo: 0,
})
expect(output).toBe(0)
})
expect(output).toBe("")
})

it("should be able to use a deep array binding", () => {
const output = processJS(`return $("foo.0.bar")`, {
foo: [
{
bar: "baz",
},
],
it("should be able to return an empty string", () => {
const output = processJS(`return $("foo")`, {
foo: "",
})
expect(output).toBe("")
})
expect(output).toBe("baz")
})

it("should handle errors", () => {
const output = processJS(`throw "Error"`)
expect(output).toBe("Error while executing JS")
})
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 timeout after one second", () => {
const output = processJS(`while (true) {}`)
expect(output).toBe("Timed out while executing JS")
})
it("should handle errors", () => {
const output = processJS(`throw "Error"`)
expect(output).toBe("Error while executing JS")
})

it("should prevent access to the process global", () => {
const output = processJS(`return process`)
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 sandbox escape", () => {
const output = processJS(
adrinr marked this conversation as resolved.
Show resolved Hide resolved
`return this.constructor.constructor("return process")()`
)
expect(output).toBe("Error while executing JS")
it("should prevent access to the process global", () => {
const output = processJS(`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")
})
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 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)
})
})
})