mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-22 07:53:34 +01:00
feat: safe mode updates
This commit is contained in:
parent
26f8dd7a7b
commit
f834eb4425
@ -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 (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xs opacity-70">Choose the app mode for this collection.</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="restricted" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="restricted"
|
||||
name="appMode"
|
||||
value="restricted"
|
||||
checked={selectedAppMode === 'restricted'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Restricted
|
||||
</label>
|
||||
<label htmlFor="safe" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="safe"
|
||||
name="appMode"
|
||||
value="safe"
|
||||
checked={selectedAppMode === 'safe'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Safe
|
||||
</label>
|
||||
<label htmlFor="developer" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="developer"
|
||||
name="appMode"
|
||||
value="developer"
|
||||
checked={selectedAppMode === 'developer'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Developer
|
||||
</label>
|
||||
</div>
|
||||
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppMode;
|
@ -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 <div className="">No Runtime. Code Execution is blocked in restricted mode.</div>;
|
||||
}
|
||||
|
||||
if (appMode === 'safe') {
|
||||
return <div className="">In Safe Mode the code execution happens isolated-vm runtime.</div>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xs opacity-70">Choose the JavaScript runtime for this collection.</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="node-vm" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="node-vm"
|
||||
name="runtime"
|
||||
value="node-vm"
|
||||
checked={selectedRuntime === 'node-vm'}
|
||||
// onChange={handleRuntimeChange}
|
||||
className="cursor-pointer opacity-50"
|
||||
/>
|
||||
Node:VM
|
||||
</label>
|
||||
<label htmlFor="vm2" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="vm2"
|
||||
name="runtime"
|
||||
value="vm2"
|
||||
checked={selectedRuntime === 'vm2'}
|
||||
onChange={handleRuntimeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
VM2
|
||||
</label>
|
||||
</div>
|
||||
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JSRuntime;
|
@ -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;
|
||||
|
@ -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 <AppMode collection={collection} />;
|
||||
}
|
||||
case 'jsRuntime': {
|
||||
return <JSRuntime collection={collection} />;
|
||||
}
|
||||
default: {
|
||||
return <div className="mt-4">404 | Not found</div>;
|
||||
}
|
||||
}
|
||||
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 (
|
||||
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('appMode')} role="tab" onClick={() => selectTab('appMode')}>
|
||||
App Mode
|
||||
</div>
|
||||
<div className={getTabClassname('jsRuntime')} role="tab" onClick={() => selectTab('jsRuntime')}>
|
||||
JS Runtime
|
||||
</div>
|
||||
<div className='font-semibold mt-2'>Scripting Sandbox</div>
|
||||
|
||||
<div className='mt-4'>
|
||||
Bruno allows JavaScript code to be executed within Variables, Scripts, Tests, and Assertions. <br/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mt-4">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="safe" className="flex flex-row items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="safe"
|
||||
name="appMode"
|
||||
value="safe"
|
||||
checked={selectedAppMode === 'safe'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<span className={selectedAppMode === 'safe' ? 'font-medium' : 'font-normal'}>
|
||||
Safe Mode
|
||||
</span>
|
||||
<span className='beta-tag'>BETA</span>
|
||||
</label>
|
||||
<p className='text-sm text-muted mt-1'>
|
||||
JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands.
|
||||
</p>
|
||||
|
||||
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="developer"
|
||||
name="appMode"
|
||||
value="developer"
|
||||
checked={selectedAppMode === 'developer'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<span className={selectedAppMode === 'developer' ? 'font-medium' : 'font-normal'}>
|
||||
Developer Mode
|
||||
<span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span>
|
||||
</span>
|
||||
</label>
|
||||
<p className='text-sm text-muted mt-1'>
|
||||
JavaScript code has access to the filesystem, execute system commands and access sensitive information.
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit mt-6">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<section className="mt-4 h-full">{getTabPanel(activeTab)}</section>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
1
packages/bruno-js/.gitignore
vendored
Normal file
1
packages/bruno-js/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
src/bundle-browser-rollup.js
|
@ -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",
|
||||
|
@ -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':
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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 },
|
||||
|
Loading…
Reference in New Issue
Block a user