Skip to content

feat(collections): update collection variables event handling#8337

Open
naman-bruno wants to merge 1 commit into
usebruno:mainfrom
naman-bruno:feat/collection-env
Open

feat(collections): update collection variables event handling#8337
naman-bruno wants to merge 1 commit into
usebruno:mainfrom
naman-bruno:feat/collection-env

Conversation

@naman-bruno

@naman-bruno naman-bruno commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Description

JIRA

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

Release Notes

  • New Features

    • Collection variables can now be modified, deleted, and retrieved directly within scripts (setCollectionVar, deleteCollectionVar, deleteAllCollectionVars, getAllCollectionVars).
    • Collection variable changes made in pre-request, post-response, and test scripts now automatically sync back to the collection.
    • Added methods for deleting global environment variables (deleteGlobalEnvVar, deleteAllGlobalEnvVars).
  • Bug Fixes

    • Improved global environment variable synchronization to strictly match incoming snapshots, removing variables that no longer exist.
  • Tests

    • Enabled comprehensive collection variable translation and scripting tests.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR re-enables previously stubbed collection variable and global environment variable mutation APIs (setCollectionVar, deleteCollectionVar, deleteAllCollectionVars, getAllCollectionVars, deleteGlobalEnvVar, deleteAllGlobalEnvVars) in the Bru class and QuickJS sandbox shim, threads collectionVariables through script and test runtimes, syncs changes back into the Electron in-memory model and Redux store via a new IPC channel (main:collection-variables-update), fixes global env var deletion in globalEnvironmentsUpdateEvent, and activates the matching Postman↔Bruno translator mappings, CodeMirror autocomplete hints, and previously skipped tests.

Changes

Collection & Global Variable API End-to-End

Layer / File(s) Summary
Bru class and QuickJS shim: collection & global var APIs
packages/bruno-js/src/bru.js, packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Re-enables and fully implements setCollectionVar, deleteCollectionVar, deleteAllCollectionVars, getAllCollectionVars, deleteGlobalEnvVar, and deleteAllGlobalEnvVars on the Bru class; exposes all six methods to the QuickJS VM via the bru shim, replacing previously commented-out TODO stubs.
Script & test runtimes: collectionVariables in result payloads
packages/bruno-js/src/runtime/script-runtime.js, packages/bruno-js/src/runtime/test-runtime.js
Adds collectionVariables (via cleanJson) to result objects returned by pre-request, post-response, and test script runtimes, including partial error result paths.
Electron main: sync and IPC notification
packages/bruno-electron/src/ipc/network/index.js
Adds syncCollectionVarsToCollection helper to merge returned collectionVariables into in-memory collection vars.req; emits main:collection-variables-update after pre-request, post-response, and test script execution in both normal and runner execution modes.
Redux: reducer, thunk, and IPC wiring
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js, .../actions.js, packages/bruno-app/src/providers/App/useIpcEvents.js
Adds _applyCollectionVariablesUpdate reducer (merge/drop vars by name, infer dataType), collectionVariablesUpdateEvent thunk (applies reducer + silent saveCollectionSettings), and IPC event hook registering the new main:collection-variables-update listener.
Global env var sync fix
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
Fixes globalEnvironmentsUpdateEvent to filter out variables absent from the incoming snapshot, so deleted global env vars are actually removed from Redux state.
Translators, autocomplete hints, and converter tests
packages/bruno-converters/src/..., packages/bruno-app/src/utils/codemirror/autocomplete.js, packages/bruno-converters/tests/...
Enables previously commented-out Postman↔Bruno translator mappings for collection variable and global env var operations in both translation directions; adds the new method names to CodeMirror autocomplete hints; re-enables all previously skipped collection-variable translator tests across multiple test files.
Redux slice tests
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.spec.js
Adds three tests for _applyCollectionVariablesUpdate: variable update/removal by name, dataType inference for typed values, and no-op behavior when collectionVariables is absent.

Sequence Diagram(s)

sequenceDiagram
  participant Bru as Bru class
  participant ScriptRuntime as ScriptRuntime/TestRuntime
  participant ElectronNet as network/index.js
  participant IpcHook as useIpcEvents
  participant Redux as Redux Store

  Bru->>Bru: setCollectionVar/deleteCollectionVar/etc.
  ScriptRuntime->>ElectronNet: collectionVariables in script/test result
  ElectronNet->>ElectronNet: syncCollectionVarsToCollection(collectionVariables)
  ElectronNet->>IpcHook: emit main:collection-variables-update
  IpcHook->>Redux: dispatch collectionVariablesUpdateEvent
  Redux->>Redux: _applyCollectionVariablesUpdate merge/drop vars
  Redux->>Redux: saveCollectionSettings silent persist to disk
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • usebruno/bruno#7052: Directly related — this PR implements the BRU collection-variable APIs (setCollectionVar, deleteCollectionVar, getAllCollectionVars) that the translator mappings in that PR emit as output.
  • usebruno/bruno#7323: Opposing change — that PR commented out and disabled the exact same collection/global var APIs, translator mappings, autocomplete hints, and tests that this PR re-enables.

Suggested labels

size/XXL

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno

Poem

🔓 The stubs are stubs no more, the TODOs swept away,
setCollectionVar speaks, deleteAll joins the fray.
IPC hums a new channel, Redux merges with care,
Translators map both ways, with tests beyond compare.
The vars flow in and out — at last, end to end! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding collection variables event handling. It's concise, clear, and reflects the primary feature across multiple modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.spec.js (1)

187-250: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one test for the “new key insertion” reducer branch.

Current cases cover update/removal and no-op, but not the incomingKeys.forEach path that creates new vars with default metadata. A focused test here will lock in uid, enabled, and inferred dataType behavior.

Suggested test addition
+  it('adds brand-new vars with defaults when script introduces a new key', () => {
+    const state = stateWith([{ uid: 'v1', name: 'keep', value: 'old', enabled: true }]);
+
+    const next = reducer(
+      state,
+      _applyCollectionVariablesUpdate({
+        collectionUid: 'col1',
+        collectionVariables: { keep: 'old', new_flag: false }
+      })
+    );
+
+    const byName = Object.fromEntries(
+      next.collections[0].draft.root.request.vars.req.map((v) => [v.name, v])
+    );
+
+    expect(byName.new_flag).toMatchObject({ name: 'new_flag', value: false, enabled: true, dataType: 'boolean' });
+    expect(byName.new_flag.uid).toBeTruthy();
+  });

As per coding guidelines: “Add tests for any new functionality or meaningful changes” and “Cover both the happy path and the realistically problematic paths.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.spec.js`
around lines 187 - 250, Add a new test case within the
_applyCollectionVariablesUpdate describe block that tests the "new key
insertion" scenario where collection variables are added that do not already
exist in the initial state. The test should use stateWith to create initial
vars, then call the reducer with _applyCollectionVariablesUpdate passing
collectionVariables containing new keys that weren't in the original state, and
verify that the reducer creates these new variables with proper uid generation,
enabled status set to true by default, and correct inferred dataType based on
the value type (number for numeric values, boolean for boolean values, undefined
for strings).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 2555-2563: The collectionVariablesUpdateEvent function
unconditionally persists collection settings whenever collectionVariables is
present, causing unnecessary disk writes even when variables haven't changed.
Before dispatching saveCollectionSettings at the end of the function, first
retrieve the existing collection variables from the collection object and
compare them with the incoming collectionVariables parameter. Only dispatch the
saveCollectionSettings call if the variables have actually changed, thereby
avoiding no-op persistence operations that can accumulate on hot request
execution paths.

In `@packages/bruno-electron/src/ipc/network/index.js`:
- Around line 510-532: The syncCollectionVarsToCollection function performs a
destructive merge that replaces root.request.vars.req with only variables
present in the collectionVariables parameter, which contains only enabled
variables (filtered upstream by mergeVars). This silently deletes any disabled
variables that existed before. Verify whether this behavior is intentional by
either preserving disabled variables from the existing root.request.vars.req
array during the merge operation (keeping disabled vars that aren't in the
incoming collectionVariables), or by modifying the upstream logic to pass the
complete snapshot of all variables instead of just the enabled-only projection.

In `@packages/bruno-js/src/bru.js`:
- Around line 263-269: Fix the crash vulnerability in deleteAllGlobalEnvVars()
and deleteAllCollectionVars() methods by using
Object.prototype.hasOwnProperty.call() instead of calling hasOwnProperty()
directly on the object, since a property named hasOwnProperty would shadow the
method and cause a TypeError. Additionally, add key validation to both
setGlobalEnvVar() and setCollectionVar() methods to reject dangerous keys like
__proto__, constructor, prototype, and hasOwnProperty before they are set,
preventing prototype poisoning attacks.

---

Nitpick comments:
In
`@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.spec.js`:
- Around line 187-250: Add a new test case within the
_applyCollectionVariablesUpdate describe block that tests the "new key
insertion" scenario where collection variables are added that do not already
exist in the initial state. The test should use stateWith to create initial
vars, then call the reducer with _applyCollectionVariablesUpdate passing
collectionVariables containing new keys that weren't in the original state, and
verify that the reducer creates these new variables with proper uid generation,
enabled status set to true by default, and correct inferred dataType based on
the value type (number for numeric values, boolean for boolean values, undefined
for strings).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 440ea1b5-68f7-4c00-9744-7bbba23c9038

📥 Commits

Reviewing files that changed from the base of the PR and between bf0e9bc and 32e3b73.

📒 Files selected for processing (22)
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.spec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-app/src/utils/codemirror/autocomplete.js
  • packages/bruno-converters/src/postman/postman-translations.js
  • packages/bruno-converters/src/utils/bruno-to-postman-translator.js
  • packages/bruno-converters/src/utils/postman-to-bruno-translator.js
  • packages/bruno-converters/tests/bruno/bruno-to-postman-translations/variables.test.js
  • packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js
  • packages/bruno-converters/tests/postman/postman-translations/postman-variables.spec.js
  • packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/combined.test.js
  • packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/multiline-syntax.test.js
  • packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js
  • packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/testing-framework.test.js
  • packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-js/src/bru.js
  • packages/bruno-js/src/runtime/script-runtime.js
  • packages/bruno-js/src/runtime/test-runtime.js
  • packages/bruno-js/src/sandbox/quickjs/shims/bru.js

Comment on lines +2555 to +2563
export const collectionVariablesUpdateEvent = ({ collectionUid, collectionVariables }) => (dispatch, getState) => {
if (!collectionVariables) return Promise.resolve();
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
if (!collection) return Promise.resolve();

dispatch(_applyCollectionVariablesUpdate({ collectionUid, collectionVariables }));
return dispatch(saveCollectionSettings(collectionUid, null, true));
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid unconditional saves for unchanged collection vars.

At Line [2562], this always persists collection settings whenever collectionVariables is present. Because this event is emitted on script execution flow, unchanged snapshots can still trigger disk writes repeatedly on request hot paths.

Proposed guard (skip no-op persistence)
 export const collectionVariablesUpdateEvent = ({ collectionUid, collectionVariables }) => (dispatch, getState) => {
   if (!collectionVariables) return Promise.resolve();
   const state = getState();
   const collection = findCollectionByUid(state.collections.collections, collectionUid);
   if (!collection) return Promise.resolve();
+
+  const currentVars = collection?.draft?.root?.request?.vars?.req || collection?.root?.request?.vars?.req || [];
+  const unchanged =
+    currentVars.length === Object.keys(collectionVariables).length
+    && currentVars.every((v) => Object.prototype.hasOwnProperty.call(collectionVariables, v.name) && collectionVariables[v.name] === v.value);
+  if (unchanged) return Promise.resolve();

   dispatch(_applyCollectionVariablesUpdate({ collectionUid, collectionVariables }));
   return dispatch(saveCollectionSettings(collectionUid, null, true));
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`
around lines 2555 - 2563, The collectionVariablesUpdateEvent function
unconditionally persists collection settings whenever collectionVariables is
present, causing unnecessary disk writes even when variables haven't changed.
Before dispatching saveCollectionSettings at the end of the function, first
retrieve the existing collection variables from the collection object and
compare them with the incoming collectionVariables parameter. Only dispatch the
saveCollectionSettings call if the variables have actually changed, thereby
avoiding no-op persistence operations that can accumulate on hot request
execution paths.

Comment on lines +510 to +532
const syncCollectionVarsToCollection = ({ collection, collectionVariables }) => {
if (!collection || !collectionVariables) return;
const applyTo = (root) => {
if (!root) return;
root.request = root.request || {};
root.request.vars = root.request.vars || {};
const existing = root.request.vars.req || [];
const incoming = new Map(Object.entries(collectionVariables));
const next = [];
for (const v of existing) {
if (!incoming.has(v.name)) continue;
next.push({ ...v, value: incoming.get(v.name) });
incoming.delete(v.name);
}
for (const [name, value] of incoming) {
next.push({ uid: uuid(), name, value, enabled: true, type: 'text' });
}
root.request.vars.req = next;
};
if (collection.draft?.root) applyTo(collection.draft.root);
if (collection.root) applyTo(collection.root);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate prepareRequest and request variable construction:"
rg -n --type=js -C5 "prepareRequest\\s*\\(|const prepareRequest|function prepareRequest" packages/bruno-electron/src

echo
echo "Trace how request.collectionVariables is built:"
rg -n --type=js -C6 "collectionVariables" packages/bruno-electron/src

echo
echo "Check whether vars.req/collection vars are filtered by enabled state upstream:"
rg -n --type=js -C6 "vars\\.req|enabled" packages/bruno-electron/src

Repository: usebruno/bruno

Length of output: 50371


🏁 Script executed:

# Search for scriptResult.collectionVariables assignment and construction
rg -n --type=js "scriptResult.*collectionVariables|collectionVariables.*scriptResult" packages/bruno-electron/src/

echo "---"

# Look for where collectionVariables is set in test/script execution
rg -n --type=js -B5 -A5 "collectionVariables:" packages/bruno-electron/src/ipc/ | head -100

echo "---"

# Check script execution context - look for test/pre-post script runners
fd -t f "*.js" packages/bruno-electron/src | xargs grep -l "preRequestScript\|postRequestScript\|scriptResult" | head -5

echo "---"

# Look for the actual script execution result building
rg -n --type=js -B10 "collectionVariables.*=" packages/bruno-electron/src/ipc/network/index.js | grep -A10 "scriptResult"

Repository: usebruno/bruno

Length of output: 10606


🏁 Script executed:

# Find where scriptResult is built - search for script execution
rg -n --type=js -B20 "scriptResult\s*=" packages/bruno-electron/src/ipc/network/index.js | head -150

echo "---"

# Look for the script runner or execution context
rg -n --type=js "executeScript|runScript|evalScript" packages/bruno-electron/src | head -20

Repository: usebruno/bruno

Length of output: 1635


🏁 Script executed:

# Find ScriptRuntime class definition
fd -type f "*.js" packages/bruno-electron/src | xargs grep -l "class ScriptRuntime"

echo "---"

# Search for ScriptRuntime and its methods
fd -type f -path "*/script-runtime*" packages/bruno-electron/src

echo "---"

# Look for the actual script runtime implementation
find packages/bruno-electron/src -name "*script*runtime*" -o -name "*runtime*script*" | head -10

Repository: usebruno/bruno

Length of output: 502


🏁 Script executed:

# Find ScriptRuntime class definition
rg -n "class ScriptRuntime" packages/bruno-electron/src

echo "---"

# Search for script-runtime files
find packages/bruno-electron/src -name "*script*" -type f | grep -i runtime

echo "---"

# Look for where scriptResult.collectionVariables is set from the script execution
rg -n "runRequestScript|runResponseScript" packages/bruno-electron/src | head -20

Repository: usebruno/bruno

Length of output: 385


🏁 Script executed:

# Search for ScriptRuntime import
rg -n "ScriptRuntime" packages/bruno-electron/src | head -30

echo "---"

# Look for the actual import statement to understand where it comes from
rg -n "import.*ScriptRuntime|require.*ScriptRuntime" packages/bruno-electron/src

echo "---"

# Search in the common or shared packages
find packages -name "*script*runtime*" -o -name "*runtime*" | grep -E "\.(js|ts)$" | head -20

Repository: usebruno/bruno

Length of output: 900


🏁 Script executed:

# Read the script-runtime.js file to understand what collectionVariables it returns
cat packages/bruno-js/src/runtime/script-runtime.js

echo "---"

# Also check the test to understand the expected behavior
rg -n "collectionVariables" packages/bruno-js/src/runtime/script-runtime.js -B5 -A5

Repository: usebruno/bruno

Length of output: 12382


🏁 Script executed:

# Now check what the Bru class does with collectionVariables during script execution
rg -n "class Bru" packages/bruno-js/src -A 50 | head -80

echo "---"

# Look for setVar or any mutation of collectionVariables in the Bru context
rg -n "collectionVariables" packages/bruno-js/src -B2 -A2 | grep -E "this\.collectionVariables|bru\.collectionVariables" | head -20

Repository: usebruno/bruno

Length of output: 7534


🏁 Script executed:

# Verify: does buildRequestScriptResult return bru.collectionVariables (modified by script) or the input collectionVariables?
rg -n "buildRequestScriptResult\|buildResponseScriptResult" packages/bruno-js/src/runtime/script-runtime.js -A 15

echo "---"

# Check if bru.collectionVariables is modified by scripts via setVar
rg -n "setVar\|getVar" packages/bruno-js/src/bru.js -B2 -A5

echo "---"

# Verify the initial request.collectionVariables comes from enabled-only filter
rg -n "collectionVariables\[" packages/bruno-electron/src/utils/collection.js -B3 -A3

Repository: usebruno/bruno

Length of output: 373


Verify full-snapshot semantics before destructive merge in syncCollectionVarsToCollection.

This helper replaces root.request.vars.req with only names present in collectionVariables. Because collectionVariables is built upstream as an enabled-only projection (see mergeVars in packages/bruno-electron/src/utils/collection.js:86 filtering if (_var.enabled)), disabled collection variables will be silently deleted and persist that deletion via collectionVariablesUpdateEvent.

Confirm whether dropped disabled vars is intentional. If not, preserve disabled vars during merge or pass full snapshot instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/ipc/network/index.js` around lines 510 - 532, The
syncCollectionVarsToCollection function performs a destructive merge that
replaces root.request.vars.req with only variables present in the
collectionVariables parameter, which contains only enabled variables (filtered
upstream by mergeVars). This silently deletes any disabled variables that
existed before. Verify whether this behavior is intentional by either preserving
disabled variables from the existing root.request.vars.req array during the
merge operation (keeping disabled vars that aren't in the incoming
collectionVariables), or by modifying the upstream logic to pass the complete
snapshot of all variables instead of just the enabled-only projection.

Comment on lines +263 to +269
deleteAllGlobalEnvVars() {
for (let key in this.globalEnvironmentVariables) {
if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
delete this.globalEnvironmentVariables[key];
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect write/delete paths on mutable variable maps:"
rg -n --type=js -C3 "setCollectionVar\\(|deleteAllCollectionVars\\(|setGlobalEnvVar\\(|deleteAllGlobalEnvVars\\(" packages/bruno-js/src/bru.js

echo
echo "Inspect direct hasOwnProperty calls on those maps:"
rg -n --type=js "globalEnvironmentVariables\\.hasOwnProperty|collectionVariables\\.hasOwnProperty" packages/bruno-js/src/bru.js

Repository: usebruno/bruno

Length of output: 1434


🏁 Script executed:

# Get full implementation of setGlobalEnvVar and setCollectionVar
sed -n '247,262p' packages/bruno-js/src/bru.js

echo "---"

sed -n '343,365p' packages/bruno-js/src/bru.js

echo "---"

# Check for variableNameRegex definition
rg -n "variableNameRegex" packages/bruno-js/src/bru.js -B2 -A2

Repository: usebruno/bruno

Length of output: 1965


🏁 Script executed:

# Check how globalEnvironmentVariables and collectionVariables are initialized
sed -n '1,100p' packages/bruno-js/src/bru.js | grep -A30 "constructor"

echo "---"

# Check if these are ever populated from untrusted sources (JSON, user input)
rg -n "globalEnvironmentVariables\s*=" packages/bruno-js/src/bru.js -B2 -A2

echo "---"

# Check if collectionVariables is populated from untrusted sources
rg -n "collectionVariables\s*=" packages/bruno-js/src/bru.js -B2 -A2

Repository: usebruno/bruno

Length of output: 2727


🏁 Script executed:

# Test the regex pattern to see if it allows dangerous keys
node -e "
const variableNameRegex = /^[\\w-.]*$/;
const dangerous = ['__proto__', 'constructor', 'prototype', 'hasOwnProperty'];
dangerous.forEach(key => {
  console.log(\`'\${key}' matches regex: \${variableNameRegex.test(key)}\`);
});
"

Repository: usebruno/bruno

Length of output: 287


🏁 Script executed:

# Check where globalEnvironmentVariables and collectionVariables are populated from
# Look for deserialization or JSON parsing
rg -n "JSON.parse|yaml.load|yaml.parse" packages/bruno-js/src/bru.js

echo "---"

# Check if these objects are ever populated from external sources (files, network, etc)
# Search the parent codebase for where Bru is instantiated
rg -rn "new Bru\(" --type=js packages/bruno-js | head -20

echo "---"

# Check if there's any object.create or Object.create with null prototype
rg -n "Object.create" packages/bruno-js/src/bru.js

Repository: usebruno/bruno

Length of output: 792


🏁 Script executed:

# Check if setGlobalEnvVar is used in ways that might pass untrusted keys
rg -n "setGlobalEnvVar" packages/bruno-js/src/bru.js -B2 -A2

echo "---"

# Verify that calling hasOwnProperty after setting hasOwnProperty as a key would fail
node -e "
const obj = {};
obj['hasOwnProperty'] = 'malicious';
try {
  obj.hasOwnProperty('test');
  console.log('No error');
} catch (e) {
  console.log('Error:', e.message);
}
"

echo "---"

# Check for prototype pollution with __proto__
node -e "
const obj1 = {};
const obj2 = {};
obj1['__proto__'] = { polluted: true };
console.log('obj2.polluted after __proto__ assignment:', obj2.polluted);
"

Repository: usebruno/bruno

Length of output: 421


🏁 Script executed:

# Verify the full risk: test if deleteAllGlobalEnvVars crashes with hasOwnProperty key
node -e "
class TestObj {
  constructor() {
    this.globalEnvironmentVariables = {};
  }
  deleteAllGlobalEnvVars() {
    for (let key in this.globalEnvironmentVariables) {
      if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
        delete this.globalEnvironmentVariables[key];
      }
    }
  }
}

const obj = new TestObj();
obj.globalEnvironmentVariables['hasOwnProperty'] = 'poison';
try {
  obj.deleteAllGlobalEnvVars();
  console.log('No error - delete succeeded');
} catch (e) {
  console.log('CRASH:', e.message);
}
"

echo "---"

# Confirm setGlobalEnvVar has NO regex validation unlike setCollectionVar
sed -n '247,251p' packages/bruno-js/src/bru.js
echo "(setGlobalEnvVar - no regex validation)"

echo "---"

sed -n '343,351p' packages/bruno-js/src/bru.js
echo "(setCollectionVar - has regex validation)"

Repository: usebruno/bruno

Length of output: 817


Fix critical crash in variable deletion and add key validation to prevent poisoning.

deleteAllGlobalEnvVars() and deleteAllCollectionVars() will crash with TypeError if a script sets a key named hasOwnProperty, because the code calls .hasOwnProperty() on the object directly. Additionally, setGlobalEnvVar() has no validation and setCollectionVar()'s regex allows dangerous keys (__proto__, constructor, prototype, hasOwnProperty).

🔧 Suggested patch
   setGlobalEnvVar(key, value) {
     if (!key) {
       throw new Error('Creating a env variable without specifying a name is not allowed.');
     }

+    if (key === '__proto__' || key === 'constructor' || key === 'prototype' || key === 'hasOwnProperty') {
+      throw new Error(`Environment variable name: "${key}" is not allowed.`);
+    }
+
     this.globalEnvironmentVariables[key] = value;
   }

   setCollectionVar(key, value) {
     if (!key) {
       throw new Error('Creating a variable without specifying a name is not allowed.');
     }

+    if (key === '__proto__' || key === 'constructor' || key === 'prototype' || key === 'hasOwnProperty') {
+      throw new Error(`Variable name: "${key}" is not allowed.`);
+    }
+
     if (variableNameRegex.test(key) === false) {
       throw new Error(
         `Variable name: "${key}" contains invalid characters!`
         + ' Names must only contain alpha-numeric characters, "-", "_", "."'
       );
     }

     this.collectionVariables[key] = value;
   }

   deleteAllGlobalEnvVars() {
-    for (let key in this.globalEnvironmentVariables) {
-      if (this.globalEnvironmentVariables.hasOwnProperty(key)) {
-        delete this.globalEnvironmentVariables[key];
-      }
-    }
+    for (const key of Object.keys(this.globalEnvironmentVariables)) {
+      delete this.globalEnvironmentVariables[key];
+    }
   }

   deleteAllCollectionVars() {
-    for (let key in this.collectionVariables) {
-      if (this.collectionVariables.hasOwnProperty(key)) {
-        delete this.collectionVariables[key];
-      }
-    }
+    for (const key of Object.keys(this.collectionVariables)) {
+      delete this.collectionVariables[key];
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-js/src/bru.js` around lines 263 - 269, Fix the crash
vulnerability in deleteAllGlobalEnvVars() and deleteAllCollectionVars() methods
by using Object.prototype.hasOwnProperty.call() instead of calling
hasOwnProperty() directly on the object, since a property named hasOwnProperty
would shadow the method and cause a TypeError. Additionally, add key validation
to both setGlobalEnvVar() and setCollectionVar() methods to reject dangerous
keys like __proto__, constructor, prototype, and hasOwnProperty before they are
set, preventing prototype poisoning attacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant