Merge pull request #12994 from Budibase/remove-vm2-from-stringtemplates

Remove vm2 from stringtemplates
This commit is contained in:
Adria Navarro 2024-02-09 13:47:40 +01:00 committed by GitHub
commit 7cbef52f77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 161 additions and 191 deletions

View File

@ -47,6 +47,13 @@ describe("jsRunner", () => {
expect(output).toBe(3) 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", () => { describe("helpers", () => {
runJsHelpersTests({ runJsHelpersTests({
funcWrap: (func: any) => config.doInContext(config.getAppId(), func), funcWrap: (func: any) => config.doInContext(config.getAppId(), func),

View File

@ -2,13 +2,13 @@
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.0.0", "version": "0.0.0",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.js",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",
"license": "MPL-2.0", "license": "MPL-2.0",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"require": "./src/index.cjs", "require": "./src/index.js",
"import": "./dist/bundle.mjs" "import": "./dist/bundle.mjs"
}, },
"./package.json": "./package.json", "./package.json": "./package.json",
@ -29,8 +29,7 @@
"@budibase/handlebars-helpers": "^0.13.1", "@budibase/handlebars-helpers": "^0.13.1",
"dayjs": "^1.10.8", "dayjs": "^1.10.8",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0"
"vm2": "^3.9.19"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-commonjs": "^17.1.0",

View File

@ -4,7 +4,7 @@ const { LITERAL_MARKER } = require("../helpers/constants")
const { getJsHelperList } = require("./list") const { getJsHelperList } = require("./list")
// The method of executing JS scripts depends on the bundle being built. // 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 let runJS
module.exports.setJSRunner = runner => (runJS = runner) module.exports.setJSRunner = runner => (runJS = runner)

View File

@ -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]
}

View File

@ -8,7 +8,7 @@ const {
doesContainString, doesContainString,
disableEscaping, disableEscaping,
findHBSBlocks, findHBSBlocks,
} = require("../src/index.cjs") } = require("../src/index.js")
describe("Test that the string processing works correctly", () => { describe("Test that the string processing works correctly", () => {
it("should process a basic template string", async () => { it("should process a basic template string", async () => {

View File

@ -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", () => { describe("Handling context properties with spaces in their name", () => {
it("should allow through literal specifiers", async () => { it("should allow through literal specifiers", async () => {

View File

@ -1,4 +1,4 @@
const { convertToJS } = require("../src/index.cjs") const { convertToJS } = require("../src/index.js")
function checkLines(response, lines) { function checkLines(response, lines) {
const toCheck = response.split("\n") const toCheck = response.split("\n")

View File

@ -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 tableJson = require("./examples/table.json")
const dayjs = require("dayjs") const dayjs = require("dayjs")
const { UUID_REGEX } = require("./constants") const { UUID_REGEX } = require("./constants")

View File

@ -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 { UUID_REGEX } = require("./constants")
const processJS = (js, context) => { const processJS = (js, context) => {
return processStringSync(encodeJSBinding(js), context) return processStringSync(encodeJSBinding(js), context)
} }
describe("Test the JavaScript helper", () => { describe("Javascript", () => {
it("should execute a simple expression", () => { beforeAll(() => {
const output = processJS(`return 1 + 2`) setJSRunner((js, context) => {
expect(output).toBe(3) return vm.runInNewContext(js, context, { timeout: 1000 })
})
it("should be able to use primitive bindings", () => {
const output = processJS(`return $("foo")`, {
foo: "bar",
}) })
expect(output).toBe("bar")
}) })
it("should be able to use an object binding", () => { describe("Test the JavaScript helper", () => {
const output = processJS(`return $("foo").bar`, { it("should execute a simple expression", () => {
foo: { const output = processJS(`return 1 + 2`)
bar: "baz", expect(output).toBe(3)
},
}) })
expect(output).toBe("baz")
})
it("should be able to use a complex object binding", () => { it("should be able to use primitive bindings", () => {
const output = processJS(`return $("foo").bar[0].baz`, { const output = processJS(`return $("foo")`, {
foo: { foo: "bar",
bar: [ })
{ expect(output).toBe("bar")
baz: "shazbat",
},
],
},
}) })
expect(output).toBe("shazbat")
})
it("should be able to use a deep binding", () => { it("should be able to use an object binding", () => {
const output = processJS(`return $("foo.bar.baz")`, { const output = processJS(`return $("foo").bar`, {
foo: { 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", 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", () => { describe("check JS helpers", () => {
const output = processJS(`throw "Error"`) it("should error if using the format helper. not helpers.", () => {
expect(output).toBe("Error while executing JS") const output = processJS(`return helper.toInt(4.3)`)
}) expect(output).toBe("Error while executing JS")
})
it("should timeout after one second", () => { it("should be able to use toInt", () => {
const output = processJS(`while (true) {}`) const output = processJS(`return helpers.toInt(4.3)`)
expect(output).toBe("Timed out while executing JS") expect(output).toBe(4)
}) })
it("should prevent access to the process global", () => { it("should be able to use uuid", () => {
const output = processJS(`return process`) const output = processJS(`return helpers.uuid()`)
expect(output).toBe("Error while executing JS") expect(output).toMatch(UUID_REGEX)
}) })
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)
}) })
}) })

View File

@ -1,3 +1,5 @@
const vm = require("vm")
jest.mock("@budibase/handlebars-helpers/lib/math", () => { jest.mock("@budibase/handlebars-helpers/lib/math", () => {
const actual = jest.requireActual("@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 tk = require("timekeeper")
const { getParsedManifest, runJsHelpersTests } = require("./utils") const { getParsedManifest, runJsHelpersTests } = require("./utils")
@ -29,6 +31,12 @@ function escapeRegExp(string) {
describe("manifest", () => { describe("manifest", () => {
const manifest = getParsedManifest() const manifest = getParsedManifest()
beforeAll(() => {
setJSRunner((js, context) => {
return vm.runInNewContext(js, context, { timeout: 1000 })
})
})
describe("examples are valid", () => { describe("examples are valid", () => {
describe.each(Object.keys(manifest))("%s", collection => { describe.each(Object.keys(manifest))("%s", collection => {
it.each(manifest[collection])("%s", async (_, { hbs, js }) => { it.each(manifest[collection])("%s", async (_, { hbs, js }) => {

View File

@ -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", () => { 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 () => { it("should be able to render the app template", async () => {

View File

@ -5,7 +5,7 @@ const {
convertToJS, convertToJS,
processStringSync, processStringSync,
encodeJSBinding, encodeJSBinding,
} = require("../src/index.cjs") } = require("../src/index.js")
function tryParseJson(str) { function tryParseJson(str) {
if (typeof str !== "string") { if (typeof str !== "string") {

View File

@ -6403,7 +6403,7 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 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" version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== 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" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== 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: vuvuzela@1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b" resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b"