diff --git a/package-lock.json b/package-lock.json index 6adf47774..80932bfc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8863,22 +8863,6 @@ "resolved": "packages/bruno-toml", "link": true }, - "node_modules/@usebruno/vm2": { - "version": "3.9.19", - "resolved": "https://registry.npmjs.org/@usebruno/vm2/-/vm2-3.9.19.tgz", - "integrity": "sha512-WIrR9ODN2xkwUEoJb3awhCZO2dTgq8NWoObofAGuzFQOQ27rw96d2GJU/T8OKcygjfJiNei9nuqidyMh81kiug==", - "license": "MIT", - "dependencies": { - "acorn": "^8.7.0", - "acorn-walk": "^8.2.0" - }, - "bin": { - "vm2": "bin/vm2" - }, - "engines": { - "node": ">=6.0" - } - }, "node_modules/@web/rollup-plugin-copy": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@web/rollup-plugin-copy/-/rollup-plugin-copy-0.5.1.tgz", @@ -9221,6 +9205,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -9254,6 +9239,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -28520,7 +28506,6 @@ "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "@usebruno/requests": "^0.1.0", - "@usebruno/vm2": "^3.9.13", "aws4-axios": "^3.3.0", "axios": "^1.8.3", "axios-ntlm": "^1.4.2", @@ -30257,7 +30242,6 @@ "@usebruno/node-machine-id": "^2.0.0", "@usebruno/requests": "^0.1.0", "@usebruno/schema": "0.7.0", - "@usebruno/vm2": "^3.9.13", "about-window": "^1.15.2", "aws4-axios": "^3.3.0", "axios": "^1.8.3", @@ -32043,9 +32027,6 @@ "@rollup/plugin-node-resolve": "^15.0.1", "rollup": "3.29.5", "rollup-plugin-terser": "^7.0.2" - }, - "peerDependencies": { - "@usebruno/vm2": "^3.9.13" } }, "packages/bruno-js/node_modules/axios": { diff --git a/packages/bruno-app/src/components/Preferences/Beta/index.js b/packages/bruno-app/src/components/Preferences/Beta/index.js index 264bc2715..0387dc486 100644 --- a/packages/bruno-app/src/components/Preferences/Beta/index.js +++ b/packages/bruno-app/src/components/Preferences/Beta/index.js @@ -8,14 +8,16 @@ import toast from 'react-hot-toast'; import { IconFlask } from '@tabler/icons'; import get from 'lodash/get'; -// Beta features configuration -const BETA_FEATURES = [ - { - id: 'nodevm', - label: 'Node VM Runtime', - description: 'Enable Node VM runtime for JavaScript execution in Developer Mode' - } -]; +/** + * Add beta features here. + * Example: + * { + * id: 'nodevm', + * label: 'Node VM Runtime', + * description: 'Enable Node VM runtime for JavaScript execution in Developer Mode' + * } + */ +const BETA_FEATURES = []; const Beta = ({ close }) => { const preferences = useSelector((state) => state.app.preferences); diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json index 99a4c907a..806f43152 100644 --- a/packages/bruno-cli/package.json +++ b/packages/bruno-cli/package.json @@ -53,7 +53,6 @@ "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "@usebruno/requests": "^0.1.0", - "@usebruno/vm2": "^3.9.13", "aws4-axios": "^3.3.0", "axios": "^1.8.3", "axios-ntlm": "^1.4.2", diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index c3a173ea1..34eb6f568 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -102,7 +102,7 @@ const printRunSummary = (results) => { }; const getJsSandboxRuntime = (sandbox) => { - return sandbox === 'safe' ? 'quickjs' : 'vm2'; + return sandbox === 'safe' ? 'quickjs' : 'nodevm'; }; const builder = async (yargs) => { diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index deb326294..18277695d 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -620,7 +620,7 @@ const runSingleRequest = async function ( envVariables, runtimeVariables, collectionPath, - null, + onConsoleLog, processEnvVars, scriptingConfig, runSingleRequestByPathname, @@ -668,7 +668,7 @@ const runSingleRequest = async function ( envVariables, runtimeVariables, collectionPath, - null, + onConsoleLog, processEnvVars, scriptingConfig, runSingleRequestByPathname, diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 4c4256dd8..a38233fe8 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -40,7 +40,6 @@ "@usebruno/node-machine-id": "^2.0.0", "@usebruno/requests": "^0.1.0", "@usebruno/schema": "0.7.0", - "@usebruno/vm2": "^3.9.13", "about-window": "^1.15.2", "aws4-axios": "^3.3.0", "axios": "^1.8.3", diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index e05126315..1bb402da9 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -59,15 +59,12 @@ const saveCookies = (url, headers) => { const getJsSandboxRuntime = (collection) => { const securityConfig = get(collection, 'securityConfig', {}); - if (securityConfig.jsSandboxMode === 'safe') { - return 'quickjs'; - } - - if (preferencesUtil.isBetaFeatureEnabled('nodevm')) { + if (securityConfig.jsSandboxMode === 'developer') { return 'nodevm'; } - return 'vm2'; + // default runtime is `quickjs` + return 'quickjs'; }; const hasStreamHeaders = (headers) => { diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 0e899d263..19dc13df3 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -41,9 +41,7 @@ const defaultPreferences = { layout: { responsePaneOrientation: 'horizontal' }, - beta: { - nodevm: false - }, + beta: {}, onboarding: { hasLaunchedBefore: false }, @@ -86,7 +84,6 @@ const preferencesSchema = Yup.object().shape({ responsePaneOrientation: Yup.string().oneOf(['horizontal', 'vertical']) }), beta: Yup.object({ - nodevm: Yup.boolean() }), onboarding: Yup.object({ hasLaunchedBefore: Yup.boolean() diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 627024242..4519adaea 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -7,9 +7,6 @@ "src", "package.json" ], - "peerDependencies": { - "@usebruno/vm2": "^3.9.13" - }, "scripts": { "test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js", "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js" diff --git a/packages/bruno-js/src/runtime/assert-runtime.js b/packages/bruno-js/src/runtime/assert-runtime.js index 3625f2004..8639ee11e 100644 --- a/packages/bruno-js/src/runtime/assert-runtime.js +++ b/packages/bruno-js/src/runtime/assert-runtime.js @@ -241,7 +241,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => { class AssertRuntime { constructor(props) { - this.runtime = props?.runtime || 'vm2'; + this.runtime = props?.runtime || 'quickjs'; } runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) { diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index bf2707b8d..27aa7d35e 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -1,45 +1,15 @@ -const { NodeVM } = require('@usebruno/vm2'); -const path = require('path'); -const http = require('http'); -const https = require('https'); -const stream = require('stream'); -const util = require('util'); -const zlib = require('zlib'); -const url = require('url'); -const punycode = require('punycode'); -const fs = require('fs'); -const { get } = require('lodash'); +const chai = require('chai'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); const { cleanJson } = require('../utils'); const { createBruTestResultMethods } = require('../utils/results'); const { runScriptInNodeVm } = require('../sandbox/node-vm'); - -// Inbuilt Library Support -const ajv = require('ajv'); -const addFormats = require('ajv-formats'); -const atob = require('atob'); -const btoa = require('btoa'); -const lodash = require('lodash'); -const moment = require('moment'); -const uuid = require('uuid'); -const nanoid = require('nanoid'); -const axios = require('axios'); -const fetch = require('node-fetch'); -const chai = require('chai'); -const CryptoJS = require('crypto-js'); -const NodeVault = require('node-vault'); -const xml2js = require('xml2js'); -const cheerio = require('cheerio'); -const tv4 = require('tv4'); -const jsonwebtoken = require('jsonwebtoken'); const { executeQuickJsVmAsync } = require('../sandbox/quickjs'); -const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays'); class ScriptRuntime { constructor(props) { - this.runtime = props?.runtime || 'vm2'; + this.runtime = props?.runtime || 'quickjs'; } // This approach is getting out of hand @@ -65,24 +35,6 @@ class ScriptRuntime { const assertionResults = request?.assertionResults || []; const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName, promptVariables); const req = new BrunoRequest(request); - const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); - const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); - const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); - const additionalContextRootsAbsolute = lodash - .chain(additionalContextRoots) - .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr))) - .value(); - - const whitelistedModules = {}; - - for (let module of moduleWhitelist) { - try { - whitelistedModules[module] = require(module); - } catch (e) { - // Ignore - console.warn(e); - } - } // extend bru with result getter methods const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); @@ -96,10 +48,6 @@ class ScriptRuntime { __brunoTestResults: __brunoTestResults }; - if (this.runtime === 'vm2') { - mixinTypedArrays(context); - } - if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { @@ -140,69 +88,12 @@ class ScriptRuntime { }; } - if (this.runtime === 'quickjs') { - await executeQuickJsVmAsync({ - script: script, - context: context, - collectionPath - }); - - return { - request, - envVariables: cleanJson(envVariables), - runtimeVariables: cleanJson(runtimeVariables), - persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - results: cleanJson(__brunoTestResults.getResults()), - nextRequestName: bru.nextRequest, - skipRequest: bru.skipRequest, - stopExecution: bru.stopExecution - }; - } - - // default runtime is vm2 - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - builtin: [ "*" ], - external: true, - root: [collectionPath, ...additionalContextRootsAbsolute], - mock: { - // node libs - path, - stream, - util, - url, - http, - https, - punycode, - zlib, - // 3rd party libs - ajv, - 'ajv-formats': addFormats, - atob, - btoa, - lodash, - moment, - uuid, - nanoid, - axios, - chai, - 'node-fetch': fetch, - 'crypto-js': CryptoJS, - xml2js: xml2js, - jsonwebtoken, - cheerio, - tv4, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } + // default runtime is `quickjs` + await executeQuickJsVmAsync({ + script: script, + context: context, + collectionPath }); - const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js')); - await asyncVM(); return { request, @@ -240,24 +131,6 @@ class ScriptRuntime { const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName, promptVariables); const req = new BrunoRequest(request); const res = new BrunoResponse(response); - const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); - const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); - const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); - const additionalContextRootsAbsolute = lodash - .chain(additionalContextRoots) - .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr))) - .value(); - - const whitelistedModules = {}; - - for (let module of moduleWhitelist) { - try { - whitelistedModules[module] = require(module); - } catch (e) { - // Ignore - console.warn(e); - } - } // extend bru with result getter methods const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); @@ -272,10 +145,6 @@ class ScriptRuntime { __brunoTestResults: __brunoTestResults }; - if (this.runtime === 'vm2') { - mixinTypedArrays(context); - } - if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { @@ -316,70 +185,13 @@ class ScriptRuntime { }; } - if (this.runtime === 'quickjs') { - await executeQuickJsVmAsync({ - script: script, - context: context, - collectionPath - }); - - return { - response, - envVariables: cleanJson(envVariables), - persistentEnvVariables: cleanJson(bru.persistentEnvVariables), - runtimeVariables: cleanJson(runtimeVariables), - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - results: cleanJson(__brunoTestResults.getResults()), - nextRequestName: bru.nextRequest, - skipRequest: bru.skipRequest, - stopExecution: bru.stopExecution - }; - } - - // default runtime is vm2 - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - builtin: [ "*" ], - external: true, - root: [collectionPath, ...additionalContextRootsAbsolute], - mock: { - // node libs - path, - stream, - util, - url, - http, - https, - punycode, - zlib, - // 3rd party libs - ajv, - 'ajv-formats': addFormats, - atob, - btoa, - lodash, - moment, - uuid, - nanoid, - axios, - 'node-fetch': fetch, - 'crypto-js': CryptoJS, - 'xml2js': xml2js, - jsonwebtoken, - cheerio, - tv4, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } + // default runtime is `quickjs` + await executeQuickJsVmAsync({ + script: script, + context: context, + collectionPath }); - const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js')); - await asyncVM(); - return { response, envVariables: cleanJson(envVariables), diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 3c89f83eb..bfdfeb578 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -1,47 +1,16 @@ -const { NodeVM } = require('@usebruno/vm2'); -const { runScriptInNodeVm } = require('../sandbox/node-vm'); const chai = require('chai'); -const path = require('path'); -const http = require('http'); -const https = require('https'); -const stream = require('stream'); -const util = require('util'); -const zlib = require('zlib'); -const url = require('url'); -const punycode = require('punycode'); -const fs = require('fs'); -const { get } = require('lodash'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); -const Test = require('../test'); -const TestResults = require('../test-results'); const { cleanJson } = require('../utils'); const { createBruTestResultMethods } = require('../utils/results'); - -// Inbuilt Library Support -const ajv = require('ajv'); -const addFormats = require('ajv-formats'); -const atob = require('atob'); -const btoa = require('btoa'); -const lodash = require('lodash'); -const moment = require('moment'); -const uuid = require('uuid'); -const nanoid = require('nanoid'); -const axios = require('axios'); -const fetch = require('node-fetch'); -const CryptoJS = require('crypto-js'); -const NodeVault = require('node-vault'); -const xml2js = require('xml2js'); -const cheerio = require('cheerio'); -const tv4 = require('tv4'); +const { runScriptInNodeVm } = require('../sandbox/node-vm'); const jsonwebtoken = require('jsonwebtoken'); const { executeQuickJsVmAsync } = require('../sandbox/quickjs'); -const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays'); class TestRuntime { constructor(props) { - this.runtime = props?.runtime || 'vm2'; + this.runtime = props?.runtime || 'quickjs'; } async runTests( @@ -66,24 +35,6 @@ class TestRuntime { const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, {}, collectionName, promptVariables); const req = new BrunoRequest(request); const res = new BrunoResponse(response); - const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); - const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); - const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); - const additionalContextRootsAbsolute = lodash - .chain(additionalContextRoots) - .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr))) - .value(); - - const whitelistedModules = {}; - - for (let module of moduleWhitelist) { - try { - whitelistedModules[module] = require(module); - } catch (e) { - // Ignore - console.warn(e); - } - } // extend bru with result getter methods const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); @@ -110,10 +61,6 @@ class TestRuntime { jwt: jsonwebtoken }; - if (this.runtime === 'vm2') { - mixinTypedArrays(context); - } - if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { @@ -136,13 +83,7 @@ class TestRuntime { let scriptError = null; try { - if (this.runtime === 'quickjs') { - await executeQuickJsVmAsync({ - script: testsFile, - context: context, - collectionPath - }); - } else if (this.runtime === 'nodevm') { + if (this.runtime === 'nodevm') { await runScriptInNodeVm({ script: testsFile, context, @@ -150,49 +91,12 @@ class TestRuntime { scriptingConfig }); } else { - // default runtime is vm2 - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - external: true, - builtin: ['*'], - root: [collectionPath, ...additionalContextRootsAbsolute], - mock: { - // node libs - path, - stream, - util, - url, - http, - https, - punycode, - zlib, - // 3rd party libs - ajv, - 'ajv-formats': addFormats, - btoa, - atob, - lodash, - moment, - uuid, - nanoid, - axios, - chai, - 'node-fetch': fetch, - 'crypto-js': CryptoJS, - 'xml2js': xml2js, - cheerio, - tv4, - 'jsonwebtoken': jsonwebtoken, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } + // default runtime is `quickjs` + await executeQuickJsVmAsync({ + script: testsFile, + context: context, + collectionPath }); - const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js')); - await asyncVM(); } } catch (error) { scriptError = error; diff --git a/packages/bruno-js/src/runtime/vars-runtime.js b/packages/bruno-js/src/runtime/vars-runtime.js index 2469d0c79..c5ff72925 100644 --- a/packages/bruno-js/src/runtime/vars-runtime.js +++ b/packages/bruno-js/src/runtime/vars-runtime.js @@ -20,7 +20,7 @@ const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => { class VarsRuntime { constructor(props) { - this.runtime = props?.runtime || 'vm2'; + this.runtime = props?.runtime || 'quickjs'; this.mode = props?.mode || 'developer'; } diff --git a/packages/bruno-js/src/sandbox/node-vm/index.js b/packages/bruno-js/src/sandbox/node-vm/index.js index 8673c2cf9..1e0a04798 100644 --- a/packages/bruno-js/src/sandbox/node-vm/index.js +++ b/packages/bruno-js/src/sandbox/node-vm/index.js @@ -3,7 +3,6 @@ const fs = require('node:fs'); const path = require('node:path'); const { get } = require('lodash'); const lodash = require('lodash'); -const { cleanJson } = require('../../utils'); const { mixinTypedArrays } = require('../mixins/typed-arrays'); class ScriptError extends Error { @@ -37,6 +36,8 @@ async function runScriptInNodeVm({ } try { + const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); + // Create script context with all necessary variables const scriptContext = { // Bruno context @@ -77,7 +78,8 @@ async function runScriptInNodeVm({ collectionPath, scriptContext, currentModuleDir: collectionPath, - localModuleCache + localModuleCache, + allowScriptFilesystemAccess }); // Execute the script in an isolated VM context @@ -105,6 +107,7 @@ async function runScriptInNodeVm({ * @param {Object} options.scriptContext - Script execution context * @param {string} options.currentModuleDir - Current module directory for relative imports * @param {Map} options.localModuleCache - Cache for loaded local modules + * @param {boolean} options.allowScriptFilesystemAccess - Whether to allow fs module access * @returns {Function} Custom require function */ function createCustomRequire({ @@ -112,7 +115,8 @@ function createCustomRequire({ collectionPath, scriptContext, currentModuleDir = collectionPath, - localModuleCache = new Map() + localModuleCache = new Map(), + allowScriptFilesystemAccess = false }) { const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); const additionalContextRootsAbsolute = lodash @@ -127,17 +131,42 @@ function createCustomRequire({ return loadLocalModule({ moduleName, collectionPath, scriptContext, localModuleCache, currentModuleDir }); } + // Helper function to check if a module is the fs module or a submodule + const isFsModule = (module) => { + if (!module) return false; + const fsModule = require('fs'); + // Check if it's the fs module itself + if (module === fsModule) return true; + // Check if it's fs/promises submodule + if (module === fsModule.promises) return true; + // Check if it's fs/promises by comparing with require('fs/promises') + try { + if (module === require('fs/promises')) return true; + } catch { + // fs/promises might not be available in all Node versions + } + return false; + }; + // First try to require as a native/npm module try { - return require(moduleName); - } catch { - // If that fails, try to resolve from additionalContextRoots - try { - const modulePath = require.resolve(moduleName, { paths: additionalContextRootsAbsolute }); - return require(modulePath); - } catch (error) { - throw new Error(`Could not resolve module "${moduleName}": ${error.message}\n\nThis most likely means you did not install the module under "additionalContextRoots" using a package manager like npm.\n\nThese are your current "additionalContextRoots":\n${additionalContextRootsAbsolute.map(root => ` - ${root}`).join('\n') || ' - No "additionalContextRoots" defined'}`); + const requiredModulePath = require.resolve(moduleName, { paths: [...additionalContextRootsAbsolute, ...module.paths] }); + const requiredModule = require(requiredModulePath); + + // Block filesystem module access if filesystem access is not allowed + if (!allowScriptFilesystemAccess && isFsModule(requiredModule)) { + throw new Error('Filesystem access is not allowed. Enable "filesystemAccess.allow" in scripting config to use the fs module.'); } + + return requiredModule; + } catch (requireError) { + // Re-throw if it's our filesystem access error + if (requireError.message && requireError.message.includes('Enable "filesystemAccess.allow"')) { + throw requireError; + } + + // If that fails, try to resolve from additionalContextRoots + throw new Error(`Could not resolve module "${moduleName}": ${requireError.message}\n\nThis most likely means you did not install the module under the collection or the "additionalContextRoots" using a package manager like npm.\n\nThese are your current "additionalContextRoots":\n${additionalContextRootsAbsolute.map((root) => ` - ${root}`).join('\n') || ' - No "additionalContextRoots" defined'}`); } }; } @@ -202,11 +231,12 @@ function loadLocalModule({ __dirname: moduleDir, // Create a custom require function for this module that resolves relative to its directory require: createCustomRequire({ - scriptingConfig: scriptContext.scriptingConfig || {}, - collectionPath, - scriptContext, - currentModuleDir: moduleDir, - localModuleCache + scriptingConfig: scriptContext.scriptingConfig || {}, + collectionPath, + scriptContext, + currentModuleDir: moduleDir, + localModuleCache, + allowScriptFilesystemAccess: get(scriptContext.scriptingConfig, 'filesystemAccess.allow', false) }) }; diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index e5fdddd33..c24a4f6ba 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -128,7 +128,7 @@ const createResponseParser = (response = {}) => { }; /** - * Objects that are created inside vm2 execution context result in an serialization error when sent to the renderer process + * Objects that are created inside developer mode execution context result in an serialization error when sent to the renderer process * Error sending from webFrameMain: Error: Failed to serialize arguments * at s.send (node:electron/js2c/browser_init:169:631) * at g.send (node:electron/js2c/browser_init:165:2156) diff --git a/packages/bruno-js/tests/runtime.spec.js b/packages/bruno-js/tests/runtime.spec.js index 3e4bbc7a0..a2d4d072d 100644 --- a/packages/bruno-js/tests/runtime.spec.js +++ b/packages/bruno-js/tests/runtime.spec.js @@ -37,7 +37,7 @@ describe('runtime', () => { }) `; - const runtime = new TestRuntime({ runtime: 'vm2' }); + const runtime = new TestRuntime({ runtime: 'nodevm' }); const result = await runtime.runTests( testFile, { ...baseRequest }, @@ -73,7 +73,7 @@ describe('runtime', () => { }) `; - const runtime = new TestRuntime({ runtime: 'vm2' }); + const runtime = new TestRuntime({ runtime: 'nodevm' }); const result = await runtime.runTests( testFile, { ...baseRequest }, @@ -116,7 +116,7 @@ describe('runtime', () => { bru.setVar('validation', validate(new Date().toISOString())) `; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env); expect(result.runtimeVariables.validation).toBeTruthy(); }); @@ -162,7 +162,7 @@ describe('runtime', () => { bru.setVar('validation', validate(new Date().toISOString())) `; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runResponseScript( script, { ...baseRequest }, @@ -181,7 +181,7 @@ describe('runtime', () => { describe('persistent environment variables validation', () => { it('should throw error when trying to persist non-string values', async () => { const script = `bru.setEnvVar('number', 42, {persist: true});`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); await expect(runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env)) .rejects.toThrow('Persistent environment variables must be strings. Received number for key "number".'); @@ -189,7 +189,7 @@ describe('runtime', () => { it('should throw error when trying to persist boolean values', async () => { const script = `bru.setEnvVar('isActive', true, {persist: true});`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); await expect(runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env)) .rejects.toThrow('Persistent environment variables must be strings. Received boolean for key "isActive".'); @@ -197,7 +197,7 @@ describe('runtime', () => { it('should throw error when trying to persist object values', async () => { const script = `bru.setEnvVar('config', {port: 3000}, {persist: true});`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); await expect(runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env)) .rejects.toThrow('Persistent environment variables must be strings. Received object for key "config".'); @@ -205,7 +205,7 @@ describe('runtime', () => { it('should throw error when trying to persist array values', async () => { const script = `bru.setEnvVar('items', ['item1', 'item2'], {persist: true});`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); await expect(runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env)) .rejects.toThrow('Persistent environment variables must be strings. Received object for key "items".'); @@ -213,7 +213,7 @@ describe('runtime', () => { it('should allow string values when persist is true', async () => { const script = `bru.setEnvVar('api_key', 'abc123', {persist: true});`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env); @@ -227,7 +227,7 @@ describe('runtime', () => { bru.setEnvVar('object', {key: 'value'}, {persist: false}); bru.setEnvVar('array', [1, 2, 3], {persist: false}); `; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env); @@ -239,7 +239,7 @@ describe('runtime', () => { it('should allow non-string values when persist is not specified', async () => { const script = `bru.setEnvVar('number', 42);`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env); @@ -251,7 +251,7 @@ describe('runtime', () => { it('should not be equal to {{$randomFirstName}}', async () => { const script = `bru.setVar('title', '{{$randomFirstName}}')`; - const runtime = new ScriptRuntime({ runtime: 'vm2' }); + const runtime = new ScriptRuntime({ runtime: 'nodevm' }); const result = await runtime.runRequestScript(script, {}, {}, {}, '.', null, process.env); diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-utils/getRandomValues.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-utils/getRandomValues.bru index c07e30530..1dd9b379b 100644 --- a/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-utils/getRandomValues.bru +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/crypto-utils/getRandomValues.bru @@ -15,12 +15,7 @@ assert { } tests { - const { doesUint8ArraysWorkAsExpected, getRandomValuesFunction, isUint8Array } = require('./scripting/inbuilt modules/utils.js'); - - if (!doesUint8ArraysWorkAsExpected()) { - console.warn('Uint8Array does not work as expected in vm2'); - return; - } + const { getRandomValuesFunction, isUint8Array } = require('./scripting/inbuilt modules/utils.js'); // check if Uint8Array work as expected test("should get random values", function() { diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/utils.js b/packages/bruno-tests/collection/scripting/inbuilt modules/utils.js index 5b9b86174..855cad82d 100644 --- a/packages/bruno-tests/collection/scripting/inbuilt modules/utils.js +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/utils.js @@ -1,19 +1,6 @@ -const doesUint8ArraysWorkAsExpected = () => { - try { - const util = require('node:util'); - // node:vm - true - // vm2 - false - return util.types.isUint8Array(new Uint8Array(32)); - } - catch (err) { - // safe mode [quickjs], will work as expected - return true; - } -} - const isUint8Array = (val) => { try { - // developer mode [node:vm and vm2] + // developer mode [node:vm] const util = require('node:util'); return util.types.isUint8Array(val); } @@ -25,7 +12,7 @@ const isUint8Array = (val) => { const getRandomValuesFunction = (typedArray) => { try { - // developer mode [node:vm and vm2] + // developer mode [node:vm] const crypto = require('node:crypto'); return crypto.getRandomValues(typedArray); } @@ -37,7 +24,7 @@ const getRandomValuesFunction = (typedArray) => { const randomBytesFunction = (num) => { try { - // developer mode [node:vm and vm2] + // developer mode [node:vm] const crypto = require('node:crypto'); return crypto.randomBytes(num); } @@ -49,7 +36,6 @@ const randomBytesFunction = (num) => { module.exports = { - doesUint8ArraysWorkAsExpected, isUint8Array, getRandomValuesFunction, randomBytesFunction diff --git a/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/bruno.json b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/bruno.json new file mode 100644 index 000000000..6130abc78 --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/bruno.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "name": "should_allow_fs", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ], + "scripts": { + "filesystemAccess": { + "allow": true + } + } +} \ No newline at end of file diff --git a/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/request.bru b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/request.bru new file mode 100644 index 000000000..2a2fd846a --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs/request.bru @@ -0,0 +1,15 @@ +meta { + name: request + type: http + seq: 1 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +} + +script:pre-request { + const fs = require('fs'); +} \ No newline at end of file diff --git a/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/bruno.json b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/bruno.json new file mode 100644 index 000000000..e751a8433 --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/bruno.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "name": "should_disallow_fs", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ], + "scripts": { + "filesystemAccess": { + "allow": false + } + } +} \ No newline at end of file diff --git a/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/request.bru b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/request.bru new file mode 100644 index 000000000..2a2fd846a --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs/request.bru @@ -0,0 +1,15 @@ +meta { + name: request + type: http + seq: 1 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +} + +script:pre-request { + const fs = require('fs'); +} \ No newline at end of file diff --git a/tests/scripting/inbuilt-libraries/fs/fs.spec.ts b/tests/scripting/inbuilt-libraries/fs/fs.spec.ts new file mode 100644 index 000000000..72471fbb7 --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/fs.spec.ts @@ -0,0 +1,80 @@ +import { test } from '../../../../playwright'; +import { setSandboxMode, runCollection, validateRunnerResults } from '../../../utils/page'; + +test.describe.serial('`fs` library', () => { + test.describe('should allow `fs` library', () => { + test('developer mode', async ({ pageWithUserData: page }) => { + test.setTimeout(2 * 60 * 1000); + + // Set up developer mode + await setSandboxMode(page, 'should_allow_fs', 'developer'); + + // Run the collection + await runCollection(page, 'should_allow_fs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 1, + failed: 0, + skipped: 0 + }); + }); + + test('safe mode', async ({ pageWithUserData: page }) => { + test.setTimeout(2 * 60 * 1000); + + // Set up safe mode + await setSandboxMode(page, 'should_allow_fs', 'safe'); + + // Run the collection + await runCollection(page, 'should_allow_fs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); + }); + }); + + test.describe('should disallow `fs` library', () => { + test('developer mode', async ({ pageWithUserData: page }) => { + test.setTimeout(2 * 60 * 1000); + + // Set up developer mode + await setSandboxMode(page, 'should_disallow_fs', 'developer'); + + // Run the collection + await runCollection(page, 'should_disallow_fs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); + }); + + test('safe mode', async ({ pageWithUserData: page }) => { + test.setTimeout(2 * 60 * 1000); + + // Set up safe mode + await setSandboxMode(page, 'should_disallow_fs', 'safe'); + + // Run the collection + await runCollection(page, 'should_disallow_fs'); + + // Validate test results + await validateRunnerResults(page, { + totalRequests: 1, + passed: 0, + failed: 1, + skipped: 0 + }); + }); + }); +}); diff --git a/tests/scripting/inbuilt-libraries/fs/init-user-data/preferences.json b/tests/scripting/inbuilt-libraries/fs/init-user-data/preferences.json new file mode 100644 index 000000000..31dc9acc8 --- /dev/null +++ b/tests/scripting/inbuilt-libraries/fs/init-user-data/preferences.json @@ -0,0 +1,7 @@ +{ + "maximized": true, + "lastOpenedCollections": [ + "{{projectRoot}}/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_allow_fs", + "{{projectRoot}}/tests/scripting/inbuilt-libraries/fs/fixtures/collections/should_disallow_fs" + ] +} \ No newline at end of file