From 4d6d4536769a18eb0c7d979a5abfbd0923dc3d45 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Oct 2021 13:44:43 +0100 Subject: [PATCH 1/4] Add one second timeout to execution of JS bindings --- packages/string-templates/src/helpers/javascript.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 301833fd34..ad8c96ae5d 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -27,6 +27,16 @@ const getContextValue = (path, context) => { return data } +// Node polyfill for base64 encoding +const btoa = plainText => { + return Buffer.from(plainText, "utf-8").toString("base64") +} + +// Node polyfill for base64 decoding +const atob = base64 => { + return Buffer.from(base64, "base64").toString("utf-8") +} + // Evaluates JS code against a certain context module.exports.processJS = (handlebars, context) => { try { @@ -39,7 +49,7 @@ module.exports.processJS = (handlebars, context) => { // Create a sandbox with out context and run the JS vm.createContext(sandboxContext) - return vm.runInNewContext(js, sandboxContext) + return vm.runInNewContext(js, sandboxContext, { timeout: 1000 }) } catch (error) { return "Error while executing JS" } From 6619a16caf04f16b52f87c7dacbb829f2fc5b3b0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Oct 2021 13:45:14 +0100 Subject: [PATCH 2/4] Add tests to HBS JS helper --- .../string-templates/test/javascript.spec.js | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/string-templates/test/javascript.spec.js diff --git a/packages/string-templates/test/javascript.spec.js b/packages/string-templates/test/javascript.spec.js new file mode 100644 index 0000000000..30ce9bbd60 --- /dev/null +++ b/packages/string-templates/test/javascript.spec.js @@ -0,0 +1,73 @@ +const { processStringSync, encodeJSBinding } = require("../src/index.cjs") + +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", + }) + expect(output).toBe("bar") + }) + + 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 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("Error while executing JS") + }) +}) From aa150989bd38746dd410a2121a40f962e6337c22 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Oct 2021 14:37:14 +0100 Subject: [PATCH 3/4] Prevent executing JS bindings when running in a Node env --- packages/string-templates/src/helpers/javascript.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index ad8c96ae5d..2c2802c64c 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -39,13 +39,23 @@ const atob = base64 => { // Evaluates JS code against a certain context module.exports.processJS = (handlebars, context) => { + // Do not evaluate JS in a node environment + if (typeof window === "undefined") { + return "JS bindings are not executed in a Node environment" + } + try { // Wrap JS in a function and immediately invoke it. // This is required to allow the final `return` statement to be valid. const js = `function run(){${atob(handlebars)}};run();` // Our $ context function gets a value from context - const sandboxContext = { $: path => getContextValue(path, context) } + const sandboxContext = { + $: path => getContextValue(path, context), + alert: undefined, + setInterval: undefined, + setTimeout: undefined, + } // Create a sandbox with out context and run the JS vm.createContext(sandboxContext) From 94f790f310da99bccd2048266a9c1c4b846c4e8e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Oct 2021 14:37:29 +0100 Subject: [PATCH 4/4] Update JS HBS helper tests --- packages/string-templates/test/javascript.spec.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/string-templates/test/javascript.spec.js b/packages/string-templates/test/javascript.spec.js index 30ce9bbd60..07cc72c298 100644 --- a/packages/string-templates/test/javascript.spec.js +++ b/packages/string-templates/test/javascript.spec.js @@ -4,7 +4,21 @@ const processJS = (js, context) => { return processStringSync(encodeJSBinding(js), context) } +describe("Test the JavaScript helper in Node", () => { + it("should not execute JS in Node", () => { + const output = processJS(`return 1`) + expect(output).toBe("JS bindings are not executed in a Node environment") + }) +}) + describe("Test the JavaScript helper", () => { + // JS bindings do not get evaluated on the server for safety. + // Since we want to run SJ for tests, we fake a window object to make + // it think that we're in the browser + beforeEach(() => { + window = {} + }) + it("should execute a simple expression", () => { const output = processJS(`return 1 + 2`) expect(output).toBe("3")