From 64cb45c09aa7bac3c7bfb67ba7ac5b3a8018dd33 Mon Sep 17 00:00:00 2001 From: lohit Date: Tue, 20 Aug 2024 19:14:03 +0530 Subject: [PATCH] feat: safe mode quickjs updates (#2866) * wip: safe mode updates * wip: safe mode quickjs updates * wip: safe mode updates * wip: safe mode updates * wip: safe mode updates --- package-lock.json | 8 ++++ packages/bruno-js/package.json | 2 + packages/bruno-js/src/runtime/test-runtime.js | 3 ++ .../bruno-js/src/sandbox/bundle-libraries.js | 4 ++ .../bruno-js/src/sandbox/quickjs/index.js | 14 +++++-- .../src/sandbox/quickjs/shims/lib/index.js | 4 +- .../sandbox/quickjs/shims/lib/node-fetch.js | 41 ------------------- .../src/sandbox/quickjs/shims/lib/path.js | 28 +++++++++++++ packages/bruno-tests/collection/lib/math.js | 4 +- packages/bruno-tests/collection/ping.bru | 5 --- .../crypto-js-pre-request-script.bru | 33 +++++++++++++++ .../inbuilt modules/nanoid/nanoid.bru | 11 ++++- .../node-fetch/node-fetch-pre-req-script.bru | 36 ---------------- .../scripting/inbuilt modules/uuid/uuid.bru | 11 ++++- 14 files changed, 113 insertions(+), 91 deletions(-) delete mode 100644 packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js create mode 100644 packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js create mode 100644 packages/bruno-tests/collection/scripting/inbuilt modules/crypto-js/crypto-js-pre-request-script.bru delete mode 100644 packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru diff --git a/package-lock.json b/package-lock.json index 6e7a537b8..0fa572df1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8340,6 +8340,12 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, + "node_modules/crypto-js-3.1.9-1": { + "name": "crypto-js", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -20595,12 +20601,14 @@ "chai": "^4.3.7", "chai-string": "^1.5.0", "crypto-js": "^4.1.1", + "crypto-js-3.1.9-1": "npm:crypto-js@^3.1.9-1", "json-query": "^2.2.2", "lodash": "^4.17.21", "moment": "^2.29.4", "nanoid": "3.3.4", "node-fetch": "^2.7.0", "node-vault": "^0.10.2", + "path": "^0.12.7", "quickjs-emscripten": "^0.29.2", "uuid": "^9.0.0" }, diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 99e028be8..22432cc08 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -25,12 +25,14 @@ "chai": "^4.3.7", "chai-string": "^1.5.0", "crypto-js": "^4.1.1", + "crypto-js-3.1.9-1": "npm:crypto-js@^3.1.9-1", "json-query": "^2.2.2", "lodash": "^4.17.21", "moment": "^2.29.4", "nanoid": "3.3.4", "node-fetch": "^2.7.0", "node-vault": "^0.10.2", + "path": "^0.12.7", "quickjs-emscripten": "^0.29.2", "uuid": "^9.0.0" }, diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 2ffcc7f6c..72f4fc519 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -84,6 +84,9 @@ class TestRuntime { }; } + // add 'await' prefix to the test function calls + testsFile = testsFile.replace(/^(?!\s*await\s)(test\([^)]*\))/gm, 'await $1'); + const context = { test, bru, diff --git a/packages/bruno-js/src/sandbox/bundle-libraries.js b/packages/bruno-js/src/sandbox/bundle-libraries.js index 23c11c426..6ecec818b 100644 --- a/packages/bruno-js/src/sandbox/bundle-libraries.js +++ b/packages/bruno-js/src/sandbox/bundle-libraries.js @@ -11,18 +11,22 @@ const bundleLibraries = async () => { import moment from "moment"; import btoa from "btoa"; import atob from "atob"; + import * as CryptoJS from "crypto-js-3.1.9-1"; globalThis.expect = expect; globalThis.assert = assert; globalThis.moment = moment; globalThis.btoa = btoa; globalThis.atob = atob; globalThis.Buffer = Buffer; + globalThis.CryptoJS = CryptoJS; globalThis.requireObject = { + ...(globalThis.requireObject || {}), 'chai': { expect, assert }, 'moment': moment, 'buffer': { Buffer }, 'btoa': btoa, 'atob': atob, + 'crypto-js': CryptoJS }; `; diff --git a/packages/bruno-js/src/sandbox/quickjs/index.js b/packages/bruno-js/src/sandbox/quickjs/index.js index 1b2cbc0a1..a18428c52 100644 --- a/packages/bruno-js/src/sandbox/quickjs/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/index.js @@ -9,6 +9,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip // execute `npm run sandbox:bundle-libraries` if the below file doesn't exist const getBundledCode = require('../bundle-browser-rollup'); +const addPathShimToContext = require('./shims/lib/path'); let QuickJSSyncContext; const loader = memoizePromiseFactory(() => newQuickJSWASMModule()); @@ -64,14 +65,14 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external const vm = module.newContext(); const bundledCode = getBundledCode?.toString() || ''; - const moduleLoaderCode = function() { + const moduleLoaderCode = function () { return ` globalThis.require = (mod) => { let lib = globalThis.requireObject[mod]; if (lib) { return lib; } - else { + else if(mod?.startsWith('.') || mod?.startsWith?.(bru.cwd())){ // fetch local module let localModuleCode = globalThis.__brunoLoadLocalModule(mod); @@ -79,7 +80,12 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external (function (){ const initModuleExportsCode = "const module = { exports: {} };" const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;"; - eval(initModuleExportsCode + localModuleCode + copyModuleExportsCode); + const patchedRequire = ${` + "\\n;" + + "let require = (subModule) => globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule))" + + "\\n;" + `} + eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode); })(); // resolve module @@ -103,6 +109,7 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external res && addBrunoResponseShimToContext(vm, res); consoleFn && addConsoleShimToContext(vm, consoleFn); addLocalModuleLoaderShimToContext(vm, collectionPath); + addPathShimToContext(vm); await addLibraryShimsToContext(vm); @@ -135,6 +142,7 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external return; } catch (error) { console.error('Error executing the script!', error); + throw new Error(error); } }; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js index 1744e3570..64f239c7f 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js @@ -1,13 +1,13 @@ const addAxiosShimToContext = require('./axios'); const addNanoidShimToContext = require('./nanoid'); -const addNodeFetchShimToContext = require('./node-fetch'); +const addPathShimToContext = require('./path'); const addUuidShimToContext = require('./uuid'); const addLibraryShimsToContext = async (vm) => { await addNanoidShimToContext(vm); await addAxiosShimToContext(vm); - await addNodeFetchShimToContext(vm); await addUuidShimToContext(vm); + await addPathShimToContext(vm); }; module.exports = addLibraryShimsToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js deleted file mode 100644 index 073b1e3c9..000000000 --- a/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js +++ /dev/null @@ -1,41 +0,0 @@ -const fetch = require('node-fetch'); -const { cleanJson } = require('../../../../utils'); -const { marshallToVm } = require('../../utils'); - -const addNodeFetchShimToContext = async (vm) => { - const nodeFetchHandle = vm.newFunction('node_fetch', (...args) => { - const nativeArgs = args.map(vm.dump); - const promise = vm.newPromise(); - fetch(...nativeArgs) - .then(async (response) => { - const { status, headers } = response || {}; - const data = await response.json(); - promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm)); - }) - .catch((err) => { - promise.resolve( - marshallToVm( - cleanJson({ - message: err.message - }), - vm - ) - ); - }); - promise.settled.then(vm.runtime.executePendingJobs); - return promise.handle; - }); - - nodeFetchHandle.consume((handle) => vm.setProp(vm.global, `__bruno__node_fetch`, handle)); - vm.evalCode( - ` - globalThis.nodeFetch = __bruno__node_fetch; - globalThis.requireObject = { - ...globalThis.requireObject, - 'node-fetch': globalThis.nodeFetch, - } - ` - ); -}; - -module.exports = addNodeFetchShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js new file mode 100644 index 000000000..8c9b2e02e --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js @@ -0,0 +1,28 @@ +const path = require('path'); +const { marshallToVm } = require('../../utils'); + +const fns = ['resolve']; + +const addPathShimToContext = async (vm) => { + fns.forEach((fn) => { + let fnHandle = vm.newFunction(fn, function (...args) { + const nativeArgs = args.map(vm.dump); + return marshallToVm(path[fn](...nativeArgs), vm); + }); + vm.setProp(vm.global, `__bruno__path__${fn}`, fnHandle); + fnHandle.dispose(); + }); + + vm.evalCode( + ` + globalThis.path = {}; + ${fns?.map((fn, idx) => `globalThis.path.${fn} = __bruno__path__${fn}`).join('\n')} + globalThis.requireObject = { + ...(globalThis.requireObject || {}), + path: globalThis.path, + } + ` + ); +}; + +module.exports = addPathShimToContext; diff --git a/packages/bruno-tests/collection/lib/math.js b/packages/bruno-tests/collection/lib/math.js index 44598e09a..c25446734 100644 --- a/packages/bruno-tests/collection/lib/math.js +++ b/packages/bruno-tests/collection/lib/math.js @@ -1,4 +1,4 @@ -const { PI } = require('./lib/constants'); +const { PI } = require('./constants'); const sum = (a, b) => a + b; const areaOfCircle = (radius) => PI * radius * radius; @@ -6,4 +6,4 @@ const areaOfCircle = (radius) => PI * radius * radius; module.exports = { sum, areaOfCircle -}; \ No newline at end of file +}; diff --git a/packages/bruno-tests/collection/ping.bru b/packages/bruno-tests/collection/ping.bru index 7083d12eb..3abc7a2d4 100644 --- a/packages/bruno-tests/collection/ping.bru +++ b/packages/bruno-tests/collection/ping.bru @@ -9,8 +9,3 @@ get { body: none auth: none } - -script:pre-request { - var CryptoJS = require("crypto-js"); - console.log(CryptoJS); -} diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-js/crypto-js-pre-request-script.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-js/crypto-js-pre-request-script.bru new file mode 100644 index 000000000..8385847c9 --- /dev/null +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-js/crypto-js-pre-request-script.bru @@ -0,0 +1,33 @@ +meta { + name: crypto-js-pre-request-script + type: http + seq: 1 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:pre-request { + var CryptoJS = require("crypto-js"); + + // Encrypt + var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString(); + + // Decrypt + var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123'); + var originalText = bytes.toString(CryptoJS.enc.Utf8); + + bru.setVar('crypto-test-message', originalText); +} + +tests { + test("crypto message", function() { + const data = bru.getVar('crypto-test-message'); + bru.setVar('crypto-test-message', null); + expect(data).to.eql('my message'); + }); + +} diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru index 47f08dc67..14aa35172 100644 --- a/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru @@ -13,5 +13,14 @@ get { script:pre-request { const { nanoid } = require("nanoid"); - req.setHeader("transaction-id", nanoid()); + bru.setVar("nanoid-test-id", nanoid()); +} + +tests { + test("nanoid var", function() { + const id = bru.getVar('nanoid-test-id'); + let isValidNanoid = /^[a-zA-Z0-9_-]{21}$/.test(id) + bru.setVar('nanoid-test-id', null); + expect(isValidNanoid).to.eql(true); + }); } diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru deleted file mode 100644 index 9821a055c..000000000 --- a/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru +++ /dev/null @@ -1,36 +0,0 @@ -meta { - name: node-fetch-pre-req-script - type: http - seq: 1 -} - -get { - url: {{host}}/ping - body: none - auth: none -} - -script:pre-request { - const fetch = require("node-fetch"); - - const url = "https://testbench-sanity.usebruno.com/api/echo/json"; - const response = await fetch(url, { - method: 'post', - body: JSON.stringify({hello:'bruno'}), - headers: {'Content-Type': 'application/json'} - }); - - req.setBody(response.data); - req.setMethod("POST"); - req.setUrl(url); -} - -tests { - test("req.getBody()", function() { - const data = res.getBody(); - expect(data).to.eql({ - "hello": "bruno" - }); - }); - -} diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru index 0a7c622a5..ba0c2edb5 100644 --- a/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru @@ -13,5 +13,14 @@ get { script:pre-request { const { v4 } = require("uuid"); - req.setHeader("transaction-id", v4()); + bru.setVar("uuid-test-id", v4()); +} + +tests { + test("uuid var", function() { + const id = bru.getVar('uuid-test-id'); + let isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id); + bru.setVar('uuid-test-id', null); + expect(isValidUuid).to.eql(true); + }); }