diff --git a/packages/bruno-app/src/components/SecuritySettings/AppMode/index.js b/packages/bruno-app/src/components/SecuritySettings/AppMode/index.js deleted file mode 100644 index 2ed27263b..000000000 --- a/packages/bruno-app/src/components/SecuritySettings/AppMode/index.js +++ /dev/null @@ -1,77 +0,0 @@ -import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions'; -import { useDispatch } from 'react-redux'; -import toast from 'react-hot-toast'; -import { useState } from 'react'; - -const AppMode = ({ collection }) => { - const dispatch = useDispatch(); - const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer'); - - const handleAppModeChange = (e) => { - setSelectedAppMode(e.target.value); - }; - - const handleSave = () => { - dispatch( - saveCollectionSecurityConfig(collection?.uid, { - appMode: selectedAppMode, - runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined - }) - ) - .then(() => { - toast.success('App Mode updated successfully'); - }) - .catch((err) => console.log(err) && toast.error('Failed to update JS AppMode')); - }; - - return ( -
-
Choose the app mode for this collection.
-
-
- - - -
- -
-
- ); -}; - -export default AppMode; diff --git a/packages/bruno-app/src/components/SecuritySettings/JSRuntime/index.js b/packages/bruno-app/src/components/SecuritySettings/JSRuntime/index.js deleted file mode 100644 index e60ffd9a0..000000000 --- a/packages/bruno-app/src/components/SecuritySettings/JSRuntime/index.js +++ /dev/null @@ -1,76 +0,0 @@ -import { cloneDeep } from 'lodash'; -import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions'; -import { useDispatch } from 'react-redux'; -import toast from 'react-hot-toast'; -import { useState } from 'react'; - -const JSRuntime = ({ collection }) => { - const dispatch = useDispatch(); - const runtime = collection?.brunoConfig?.security?.runtime; - const appMode = collection?.brunoConfig?.security?.appMode; - const [selectedRuntime, setSelectedRuntime] = useState(runtime || 'vm2'); - - if (appMode === 'restricted') { - return
No Runtime. Code Execution is blocked in restricted mode.
; - } - - if (appMode === 'safe') { - return
In Safe Mode the code execution happens isolated-vm runtime.
; - } - - const handleRuntimeChange = (e) => { - setSelectedRuntime(e.target.value); - }; - - const handleSave = () => { - let brunoConfig = cloneDeep(collection.brunoConfig) || {}; - brunoConfig.security = { - ...(brunoConfig?.security || {}), - runtime: selectedRuntime - }; - dispatch(updateBrunoConfig(brunoConfig, collection.uid)) - .then(() => { - toast.success('JS Runtime updated successfully'); - }) - .catch((err) => console.log(err) && toast.error('Failed to update JS Runtime')); - }; - - return ( -
-
Choose the JavaScript runtime for this collection.
-
-
- - -
- -
-
- ); -}; - -export default JSRuntime; diff --git a/packages/bruno-app/src/components/SecuritySettings/StyledWrapper.js b/packages/bruno-app/src/components/SecuritySettings/StyledWrapper.js index b88a31e0d..62777f75b 100644 --- a/packages/bruno-app/src/components/SecuritySettings/StyledWrapper.js +++ b/packages/bruno-app/src/components/SecuritySettings/StyledWrapper.js @@ -3,6 +3,21 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` max-width: 800px; + span.beta-tag { + display: flex; + align-items: center; + padding: 0.1rem 0.25rem; + font-size: 0.75rem; + border-radius: 0.25rem; + color: ${(props) => props.theme.colors.text.green}; + border: solid 1px ${(props) => props.theme.colors.text.green} !important; + } + + span.developer-mode-warning { + font-weight: 400; + color: ${(props) => props.theme.colors.text.yellow}; + } + div.tabs { div.tab { padding: 6px 0px; diff --git a/packages/bruno-app/src/components/SecuritySettings/index.js b/packages/bruno-app/src/components/SecuritySettings/index.js index 5e4768abe..4ab3f2c02 100644 --- a/packages/bruno-app/src/components/SecuritySettings/index.js +++ b/packages/bruno-app/src/components/SecuritySettings/index.js @@ -1,52 +1,82 @@ +import { useState } from 'react'; +import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions'; import classnames from 'classnames'; import StyledWrapper from './StyledWrapper'; import { useDispatch } from 'react-redux'; -import { updateSecuritySettingsSelectedTab } from 'providers/ReduxStore/slices/collections/index'; -import JSRuntime from './JSRuntime/index'; -import AppMode from './AppMode/index'; const SecuritySettings = ({ collection }) => { const dispatch = useDispatch(); - const activeTab = collection.securitySettingsSelectedTab || 'appMode'; - const selectTab = (tab) => { - dispatch( - updateSecuritySettingsSelectedTab({ - collectionUid: collection.uid, - tab - }) - ); - }; - const getTabPanel = (tab) => { - switch (tab) { - case 'appMode': { - return ; - } - case 'jsRuntime': { - return ; - } - default: { - return
404 | Not found
; - } - } + const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer'); + + const handleAppModeChange = (e) => { + setSelectedAppMode(e.target.value); }; - const getTabClassname = (tabName) => { - return classnames(`tab select-none ${tabName}`, { - active: tabName === activeTab - }); + const handleSave = () => { + dispatch( + saveCollectionSecurityConfig(collection?.uid, { + appMode: selectedAppMode, + runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined + }) + ) + .then(() => { + toast.success('App Mode updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update JS AppMode')); }; return ( -
-
selectTab('appMode')}> - App Mode -
-
selectTab('jsRuntime')}> - JS Runtime -
+
Scripting Sandbox
+ +
+ Bruno allows JavaScript code to be executed within Variables, Scripts, Tests, and Assertions.
+
+ +
+
+ +

+ JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands. +

+ + +

+ JavaScript code has access to the filesystem, execute system commands and access sensitive information. +

+
+
-
{getTabPanel(activeTab)}
); }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index a91a6fb0d..85eb5e99f 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -33,7 +33,6 @@ export const collectionsSlice = createSlice({ const collection = action.payload; collection.settingsSelectedTab = 'headers'; - collection.securitySettingsSelectedTab = 'appMode'; collection.showAppModeModal = !collection?.securityConfig?.appMode; @@ -1629,15 +1628,6 @@ export const collectionsSlice = createSlice({ item.draft.request.docs = action.payload.docs; } } - }, - updateSecuritySettingsSelectedTab: (state, action) => { - const { collectionUid, tab } = action.payload; - - const collection = findCollectionByUid(state.collections, collectionUid); - - if (collection) { - collection.securitySettingsSelectedTab = tab; - } } } }); @@ -1729,7 +1719,6 @@ export const { runFolderEvent, resetCollectionRunner, updateRequestDocs, - updateSecuritySettingsSelectedTab, setShowAppModeModal } = collectionsSlice.actions; diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 63369e149..78daa8d6a 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -57,7 +57,7 @@ const runSingleRequest = async function ( // run pre-request vars const preRequestVars = get(bruJson, 'request.vars.req'); if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); varsRuntime.runPreRequestVars( preRequestVars, request, @@ -74,7 +74,7 @@ const runSingleRequest = async function ( get(bruJson, 'request.script.req') ]).join(os.EOL); if (requestScriptFile?.length) { - const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); const result = await scriptRuntime.runRequestScript( decomment(requestScriptFile), request, @@ -276,7 +276,7 @@ const runSingleRequest = async function ( // run post-response vars const postResponseVars = get(bruJson, 'request.vars.res'); if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); varsRuntime.runPostResponseVars( postResponseVars, request, @@ -294,7 +294,7 @@ const runSingleRequest = async function ( get(bruJson, 'request.script.res') ]).join(os.EOL); if (responseScriptFile?.length) { - const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); const result = await scriptRuntime.runResponseScript( decomment(responseScriptFile), request, @@ -315,7 +315,7 @@ const runSingleRequest = async function ( let assertionResults = []; const assertions = get(bruJson, 'request.assertions'); if (assertions) { - const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime }); assertionResults = assertRuntime.runAssertions( assertions, request, @@ -339,7 +339,7 @@ const runSingleRequest = async function ( let testResults = []; const testFile = compact([get(collectionRoot, 'request.tests'), get(bruJson, 'request.tests')]).join(os.EOL); if (typeof testFile === 'string') { - const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime }); const result = await testRuntime.runTests( decomment(testFile), request, diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 504fde624..466702d6a 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -47,7 +47,6 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "iconv-lite": "^0.6.3", - "is-number": "^7.0.0", "is-valid-path": "^0.1.1", "js-yaml": "^4.1.0", "json-bigint": "^1.0.0", @@ -66,7 +65,7 @@ "dmg-license": "^1.0.11" }, "devDependencies": { - "electron": "31.2.1", + "electron": "21.1.1", "electron-builder": "23.0.2", "electron-icon-maker": "^0.0.5" } diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 748633045..582812f94 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -670,7 +670,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:save-collection-security-config', async (event, collectionPath, securityConfig) => { try { - collectionSecurityStore.storeSecurityConfigForCollection(collectionPath, securityConfig); + collectionSecurityStore.setSecurityConfigForCollection(collectionPath, securityConfig); } catch (error) { return Promise.reject(error); } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 1b23b838d..972cf52d9 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -315,7 +315,7 @@ const registerNetworkIpc = (mainWindow) => { // run pre-request vars const preRequestVars = get(request, 'vars.req', []); if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); varsRuntime.runPreRequestVars( preRequestVars, request, @@ -330,7 +330,7 @@ const registerNetworkIpc = (mainWindow) => { let scriptResult; const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL); if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); scriptResult = await scriptRuntime.runRequestScript( decomment(requestScript), request, @@ -382,7 +382,7 @@ const registerNetworkIpc = (mainWindow) => { // run post-response vars const postResponseVars = get(request, 'vars.res', []); if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime }); const result = varsRuntime.runPostResponseVars( postResponseVars, request, @@ -416,7 +416,7 @@ const registerNetworkIpc = (mainWindow) => { let scriptResult; if (responseScript?.length) { - const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); scriptResult = await scriptRuntime.runResponseScript( decomment(responseScript), request, @@ -575,7 +575,7 @@ const registerNetworkIpc = (mainWindow) => { // run assertions const assertions = get(request, 'assertions'); if (assertions) { - const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime }); const results = assertRuntime.runAssertions( assertions, request, @@ -603,7 +603,7 @@ const registerNetworkIpc = (mainWindow) => { ]).join(os.EOL); if (typeof testFile === 'string') { - const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode }); + const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime }); const testResults = await testRuntime.runTests( decomment(testFile), request, @@ -1028,10 +1028,7 @@ const registerNetworkIpc = (mainWindow) => { // run assertions const assertions = get(item, 'request.assertions'); if (assertions) { - const assertRuntime = new AssertRuntime({ - runtime: scriptingConfig?.runtime, - mode: scriptingConfig?.appMode - }); + const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime }); const results = assertRuntime.runAssertions( assertions, request, @@ -1058,10 +1055,7 @@ const registerNetworkIpc = (mainWindow) => { ]).join(os.EOL); if (typeof testFile === 'string') { - const testRuntime = new TestRuntime({ - runtime: scriptingConfig?.runtime, - mode: scriptingConfig?.appMode - }); + const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime }); const testResults = await testRuntime.runTests( decomment(testFile), request, diff --git a/packages/bruno-electron/src/store/collection-security.js b/packages/bruno-electron/src/store/collection-security.js index d1ca189c3..07a572ca6 100644 --- a/packages/bruno-electron/src/store/collection-security.js +++ b/packages/bruno-electron/src/store/collection-security.js @@ -9,7 +9,7 @@ class CollectionSecurityStore { }); } - storeSecurityConfigForCollection(collectionPathname, securityConfig) { + setSecurityConfigForCollection(collectionPathname, securityConfig) { const collections = this.store.get('collections') || []; const collection = _.find(collections, (c) => c.path === collectionPathname); diff --git a/packages/bruno-js/.gitignore b/packages/bruno-js/.gitignore new file mode 100644 index 000000000..c7c43f854 --- /dev/null +++ b/packages/bruno-js/.gitignore @@ -0,0 +1 @@ +src/bundle-browser-rollup.js \ No newline at end of file diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 26d311c8c..345f9da5c 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -17,7 +17,7 @@ "install:isolated-vm": "cd ./node_modules/isolated-vm && npm install", "prebuild:isolated-vm:dev": "node ./scripts/prebuild-isolated-vm-for-dev.js", "prebuild:isolated-vm:prod": "node ./scripts/prebuild-isolated-vm-for-prod-builds.js", - "build:isolated-vm:inbuilt-modules": "node ./src/sandbox/isolatedvm/utils/bundleLibraries.js" + "build:isolated-vm:inbuilt-modules": "node ./src/sandbox/isolatedvm/utils/bundle-libraries.js" }, "dependencies": { "@faker-js/faker": "^8.4.1", diff --git a/packages/bruno-js/src/runtime/assert-runtime.js b/packages/bruno-js/src/runtime/assert-runtime.js index 95f725d0d..adc67ff61 100644 --- a/packages/bruno-js/src/runtime/assert-runtime.js +++ b/packages/bruno-js/src/runtime/assert-runtime.js @@ -162,58 +162,31 @@ const isUnaryOperator = (operator) => { return unaryOperators.includes(operator); }; -const toNumber = (value) => { - const num = Number(value); - return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value); -}; - -const evaluateJsTemplateLiteralBasedOnRuntime = (v, context, runtime, mode) => { - let value; - if (mode === 'restricted') { - let _value = _.get(context, v, v); - if (_value && typeof _value == 'object') { - value = JSON.stringify(_value); - } else if (Number.isNaN(Number(_value))) { - value = _value; - } else { - value = toNumber(_value); - } - } else if (mode === 'safe') { - value = isolatedVMStrictInstance.execute({ - script: v, +const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => { + if(runtime === 'isolated-vm') { + return isolatedVMStrictInstance.execute({ + script: literal, context, scriptType: 'template-literal' }); - } else { - value = evaluateJsTemplateLiteral(v, context); } - return value; + + return evaluateJsTemplateLiteral(literal, context); }; -const evaluateJsExpressionBasedOnRuntime = (v, context, runtime, mode) => { - let value; - if (mode === 'restricted') { - let _value = _.get(context, v, v); - if (_value && typeof _value == 'object') { - value = JSON.stringify(_value); - } else if (Number.isNaN(Number(_value))) { - value = _value; - } else { - value = toNumber(_value); - } - } else if (mode === 'safe') { - value = isolatedVMStrictInstance.execute({ - script: v, +const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime) => { + if(runtime === 'isolated-vm') { + return isolatedVMStrictInstance.execute({ + script: expr, context, scriptType: 'expression' }); - } else { - value = evaluateJsExpression(v, context); } - return value; -}; -const evaluateRhsOperand = (rhsOperand, operator, context, runtime, mode) => { + return evaluateJsExpression(expr, context); +} + +const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => { if (isUnaryOperator(operator)) { return; } @@ -237,8 +210,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime, mode) => { evaluateJsTemplateLiteralBasedOnRuntime( interpolateString(v.trim(), interpolationContext), context, - runtime, - mode + runtime ) ); } @@ -250,8 +222,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime, mode) => { evaluateJsTemplateLiteralBasedOnRuntime( interpolateString(v.trim(), interpolationContext), context, - runtime, - mode + runtime ) ); return [lhs, rhs]; @@ -269,15 +240,13 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime, mode) => { return evaluateJsTemplateLiteralBasedOnRuntime( interpolateString(rhsOperand, interpolationContext), context, - runtime, - mode + runtime ); }; class AssertRuntime { constructor(props) { this.runtime = props?.runtime || 'vm2'; - this.mode = props?.mode || 'developer'; } runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) { @@ -314,8 +283,8 @@ class AssertRuntime { const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr); try { - const lhs = evaluateJsExpressionBasedOnRuntime(lhsExpr, context, this.runtime, this.mode); - const rhs = evaluateRhsOperand(rhsOperand, operator, context, this.runtime, this.mode); + const lhs = evaluateJsExpressionBasedOnRuntime(lhsExpr, context, this.runtime); + const rhs = evaluateRhsOperand(rhsOperand, operator, context, this.runtime); switch (operator) { case 'eq': diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index bc121f59b..68c64784f 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -28,12 +28,11 @@ const fetch = require('node-fetch'); const chai = require('chai'); const CryptoJS = require('crypto-js'); const NodeVault = require('node-vault'); -const { isolatedVMAsyncInstance, executeInIsolatedVMAsync } = require('../sandbox/isolatedvm'); +const { executeInIsolatedVMAsync } = require('../sandbox/isolatedvm'); class ScriptRuntime { constructor(props) { this.runtime = props?.runtime || 'vm2'; - this.mode = props?.mode || 'developer'; } // This approach is getting out of hand @@ -48,14 +47,6 @@ class ScriptRuntime { processEnvVars, scriptingConfig ) { - if (this.mode === 'restricted') { - return { - request, - envVariables: cleanJson(envVariables), - collectionVariables: cleanJson(collectionVariables), - nextRequestName: undefined - }; - } const requestVariables = request?.requestVariables || {}; const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); const req = new BrunoRequest(request); @@ -98,7 +89,7 @@ class ScriptRuntime { }; } - if (this.mode == 'safe') { + if (this.runtime === 'isolated-vm') { // Reuses the same instance of IsolatedVMAsync // TODO: Test for performance // await isolatedVMAsyncInstance.execute({ @@ -113,53 +104,54 @@ class ScriptRuntime { modules: {}, scriptType: 'jsScript' }); - } else { - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - 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, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } - }); - const asyncVM = vm.run( - `module.exports = async () => { - console?.debug && console.debug('vm2:pre-request:execution-start'); - ${script} - console?.debug && console.debug('vm2:pre-request:execution-end:'); - }`, - path.join(collectionPath, 'vm.js') - ); - await asyncVM(); + + return { + request, + envVariables: cleanJson(envVariables), + runtimeVariables: cleanJson(runtimeVariables), + nextRequestName: bru.nextRequest + }; } + // default runtime is vm2 + const vm = new NodeVM({ + sandbox: context, + require: { + context: 'sandbox', + 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, + ...whitelistedModules, + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault + } + } + }); + const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js')); + await asyncVM(); + return { request, envVariables: cleanJson(envVariables), @@ -179,14 +171,6 @@ class ScriptRuntime { processEnvVars, scriptingConfig ) { - if (this.mode === 'restricted') { - return { - request, - envVariables: cleanJson(envVariables), - collectionVariables: cleanJson(collectionVariables), - nextRequestName: undefined - }; - } const requestVariables = request?.requestVariables || {}; const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); const req = new BrunoRequest(request); @@ -226,7 +210,7 @@ class ScriptRuntime { }; } - if (this.mode == 'safe') { + if (this.runtime === 'isolated-vm') { // Reuses the same instance of IsolatedVMAsync // TODO: Test for performance // await isolatedVMAsyncInstance.execute({ @@ -241,54 +225,54 @@ class ScriptRuntime { modules: {}, scriptType: 'jsScript' }); - } else { - // DEVELOPER MODE - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - external: true, - root: [collectionPath], - 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, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } - }); - const asyncVM = vm.run( - `module.exports = async () => { - console?.debug && console.debug('vm2:post-response:execution-start:'); - ${script} - console?.debug && console.debug('vm2:post-response:execution-end:'); - }`, - path.join(collectionPath, 'vm.js') - ); - await asyncVM(); + return { + response, + envVariables: cleanJson(envVariables), + runtimeVariables: cleanJson(runtimeVariables), + nextRequestName: bru.nextRequest + }; } + // default runtime is vm2 + const vm = new NodeVM({ + sandbox: context, + require: { + context: 'sandbox', + external: true, + root: [collectionPath], + 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, + ...whitelistedModules, + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault + } + } + }); + + 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 615c98904..72565d2e2 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -30,12 +30,11 @@ const axios = require('axios'); const fetch = require('node-fetch'); const CryptoJS = require('crypto-js'); const NodeVault = require('node-vault'); -const { executeInIsolatedVMAsync, isolatedVMAsyncInstance } = require('../sandbox/isolatedvm'); +const { executeInIsolatedVMAsync } = require('../sandbox/isolatedvm'); class TestRuntime { constructor(props) { this.runtime = props?.runtime || 'vm2'; - this.mode = props?.mode || 'developer'; } async runTests( @@ -49,14 +48,6 @@ class TestRuntime { processEnvVars, scriptingConfig ) { - if (this.mode === 'restricted') { - return { - request, - envVariables: cleanJson(envVariables), - collectionVariables: cleanJson(collectionVariables), - results: [] - }; - } const requestVariables = request?.requestVariables || {}; const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables); const req = new BrunoRequest(request); @@ -132,54 +123,55 @@ class TestRuntime { modules: {}, scriptType: 'jsScript' }); - } else { - // DEVELOPER MODE - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - external: true, - 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, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault - } - } - }); - const asyncVM = vm.run( - `module.exports = async () => { - console?.debug && console.debug('vm2:tests:execution-start'); - ${testsFile} - console?.debug && console.debug('vm2:tests:execution-end:'); - }`, - path.join(collectionPath, 'vm.js') - ); - await asyncVM(); + + return { + request, + envVariables: cleanJson(envVariables), + runtimeVariables: cleanJson(runtimeVariables), + results: cleanJson(__brunoTestResults.getResults()), + nextRequestName: bru.nextRequest + }; } + // default runtime is vm2 + const vm = new NodeVM({ + sandbox: context, + require: { + context: 'sandbox', + external: true, + 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, + ...whitelistedModules, + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault + } + } + }); + const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js')); + await asyncVM(); + return { request, envVariables: cleanJson(envVariables), diff --git a/packages/bruno-js/src/runtime/vars-runtime.js b/packages/bruno-js/src/runtime/vars-runtime.js index ef92bc936..ef04511a5 100644 --- a/packages/bruno-js/src/runtime/vars-runtime.js +++ b/packages/bruno-js/src/runtime/vars-runtime.js @@ -5,55 +5,28 @@ const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } const { isolatedVMStrictInstance } = require('../sandbox/isolatedvm'); -const toNumber = (value) => { - const num = Number(value); - return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value); -}; - -const evaluateJsTemplateLiteralBasedOnRuntime = (v, context, runtime, mode) => { - let value; - if (mode === 'restricted') { - let _value = _.get(context, v, v); - if (_value && typeof _value == 'object') { - value = JSON.stringify(_value); - } else if (Number.isNaN(Number(_value))) { - value = _value; - } else { - value = toNumber(_value); - } - } else if (mode === 'safe') { - value = isolatedVMStrictInstance.execute({ - script: v, +const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => { + if(runtime === 'isolated-vm') { + return isolatedVMStrictInstance.execute({ + script: literal, context, scriptType: 'template-literal' }); - } else { - value = evaluateJsTemplateLiteral(v, context); } - return value; + + return evaluateJsTemplateLiteral(literal, context); }; -const evaluateJsExpressionBasedOnRuntime = (v, context, runtime, mode) => { - let value; - if (mode === 'restricted') { - let _value = _.get(context, v, v); - if (_value && typeof _value == 'object') { - value = JSON.stringify(_value); - } else if (Number.isNaN(Number(_value))) { - value = _value; - } else { - value = toNumber(_value); - } - } else if (mode === 'safe') { - value = isolatedVMStrictInstance.execute({ - script: v, +const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => { + if(runtime === 'isolated-vm') { + return isolatedVMStrictInstance.execute({ + script: expr, context, scriptType: 'expression' }); - } else { - value = evaluateJsExpression(v, context); } - return value; + + return evaluateJsExpression(expr, context); }; class VarsRuntime { @@ -86,7 +59,7 @@ class VarsRuntime { }; _.each(enabledVars, (v) => { - const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime, this.mode); + const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime); request?.requestVariables && (request.requestVariables[v.name] = value); }); } @@ -117,7 +90,7 @@ class VarsRuntime { const errors = new Map(); _.each(enabledVars, (v) => { try { - const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime, this.mode); + const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime); bru.setVar(v.name, value); } catch (error) { errors.set(v.name, error); diff --git a/packages/bruno-js/src/sandbox/isolatedvm/index.js b/packages/bruno-js/src/sandbox/isolatedvm/index.js index ac361ef9e..c98e38e3e 100644 --- a/packages/bruno-js/src/sandbox/isolatedvm/index.js +++ b/packages/bruno-js/src/sandbox/isolatedvm/index.js @@ -1,8 +1,8 @@ const ivm = require('isolated-vm'); const addBruShimToContext = require('./shims/bru'); -const addBrunoRequestShimToContext = require('./shims/brunoRequest'); +const addBrunoRequestShimToContext = require('./shims/bruno-request'); const addConsoleShimToContext = require('./shims/console'); -const addBrunoResponseShimToContext = require('./shims/brunoResponse'); +const addBrunoResponseShimToContext = require('./shims/bruno-response'); const addTestShimToContext = require('./shims/test'); const fs = require('fs'); const addLibraryShimsToContext = require('./shims/lib'); diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/brunoRequest.js b/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-request.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/shims/brunoRequest.js rename to packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-request.js diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/brunoResponse.js b/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-response.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/shims/brunoResponse.js rename to packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-response.js diff --git a/packages/bruno-js/src/sandbox/isolatedvm/utils/bundleLibraries.js b/packages/bruno-js/src/sandbox/isolatedvm/utils/bundle-libraries.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/utils/bundleLibraries.js rename to packages/bruno-js/src/sandbox/isolatedvm/utils/bundle-libraries.js diff --git a/packages/bruno-js/tests/runtime.spec.js b/packages/bruno-js/tests/runtime.spec.js index 46ea62fb7..766569d03 100644 --- a/packages/bruno-js/tests/runtime.spec.js +++ b/packages/bruno-js/tests/runtime.spec.js @@ -114,7 +114,7 @@ describe('runtime', () => { bru.setVar('validation', validate(new Date().toISOString())) `; - const runtime = new ScriptRuntime({ runtime: 'vm2', mode: 'developer' }); + const runtime = new ScriptRuntime({ runtime: 'vm2' }); const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env); expect(result.runtimeVariables.validation).toBeTruthy(); }); @@ -160,7 +160,7 @@ describe('runtime', () => { bru.setVar('validation', validate(new Date().toISOString())) `; - const runtime = new ScriptRuntime({ runtime: 'vm2', mode: 'developer' }); + const runtime = new ScriptRuntime({ runtime: 'vm2' }); const result = await runtime.runResponseScript( script, { ...baseRequest },