Skip to content

fix(secrets): block duplicate secret names to prevent value loss#8750

Open
pooja-bruno wants to merge 2 commits into
usebruno:mainfrom
pooja-bruno:fix/env-duplicate-secret-names
Open

fix(secrets): block duplicate secret names to prevent value loss#8750
pooja-bruno wants to merge 2 commits into
usebruno:mainfrom
pooja-bruno:fix/env-duplicate-secret-names

Conversation

@pooja-bruno

@pooja-bruno pooja-bruno commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes a data-loss bug in the Secrets tab (collection and global environments).

Adding two secrets with the same name and saving silently dropped the first one's value. Secret values are persisted in a separate name-keyed store and re-hydrated by first-match on read, so a second secret with a duplicate name overwrote the first on save. (The Variables tab is unaffected those values live inline in the .bru/env file, so duplicates are allowed there and lose nothing.

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

  • New Features

    • Environment tabs now display counts of enabled variables and secrets.
    • Unsaved changes are highlighted directly on the tab count.
  • Bug Fixes

    • Duplicate secret names are now detected and prevented from being saved.
    • Inline validation identifies the most recently edited duplicate entry.
    • Save errors now provide a specific duplicate-name message.
  • Tests

    • Added coverage for duplicate secret validation, save prevention, and unsaved tab indicators.
Screen.Recording.2026-07-23.at.6.16.36.PM.mov

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Duplicate secret names are now validated in environment editors and save flows. Inline errors target the edited row, duplicate saves show a dedicated toast, and environment tabs display enabled-variable counts with unsaved styling. Tests and locators cover both collection and global environments.

Changes

Environment validation and indicators

Layer / File(s) Summary
Duplicate secret detection and save guards
packages/bruno-app/src/utils/environments.js, packages/bruno-app/src/utils/environments.spec.js, packages/bruno-app/src/providers/ReduxStore/slices/{collections/actions.js,global-environments.js}
Adds duplicate secret-name detection and rejects collection or global environment saves containing duplicates.
Editor validation and save error handling
packages/bruno-app/src/components/EnvironmentVariablesTable/index.js, packages/bruno-app/src/components/RequestTabs/RequestTab/index.js, tests/utils/page/*, tests/environments/environment-tabs/environment-tabs.spec.ts
Targets inline errors to the most recently edited duplicate row, validates active and all-tab saves, and displays dedicated duplicate-name toasts with coverage for both environment types.
Count-based environment tab indicators
packages/bruno-app/src/components/{Environments,WorkspaceHome}/.../EnvironmentDetails/*, tests/utils/page/locators.ts, tests/environments/environment-tabs/environment-tabs.spec.ts
Replaces draft icons with enabled named-variable counts and marks changed counts as unsaved. Tests verify per-tab count and save behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: bijin-bruno, lohit-bruno, sid-bruno

Sequence Diagram(s)

sequenceDiagram
  participant EnvironmentEditor
  participant SaveFlow
  participant RequestTab
  participant Toast
  EnvironmentEditor->>SaveFlow: submit environment variables
  SaveFlow->>SaveFlow: detect duplicate secret names
  SaveFlow-->>RequestTab: reject duplicate save
  RequestTab->>Toast: show duplicate-name error
Loading

Poem

Secrets align in tidy rows,
Duplicate names now raise a sign,
Tabs count softly as change grows,
Unsaved badges brightly shine.
Save flows guard the finish line.

🚥 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 clearly summarizes the main change: blocking duplicate secret names to prevent data loss.
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.
✨ 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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (1)

257-281: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use showSaveError in both Save & Close flows.

The handlers at Lines 443-446 and 494-497 still replace the duplicate-secret rejection with generic errors, so closing a tab after creating duplicate secrets does not show the new actionable message.

Proposed fix
- .catch((err) => {
-   console.log('err', err);
-   toast.error('Failed to save environment');
- });
+ .catch(showSaveError);

Apply the equivalent change to the global-environment Save & Close handler.

🤖 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/components/RequestTabs/RequestTab/index.js` around
lines 257 - 281, Update both Save & Close handlers, including the
global-environment handler, to use the existing showSaveError callback when
their save promises reject. Replace the generic error toast handling while
preserving the current success behavior and duplicate-secret message.
🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts`:
- Around line 181-207: Update the environment-tab scenarios at
tests/environments/environment-tabs/environment-tabs.spec.ts:181-207 and
:579-605 to assert the visible Variables and Secrets tab-count text after each
row is added and again after saving, using the expected enabled-item counts.
Retain the existing unsaved-class assertions, and apply the same count-text
coverage to the global-environments scenario.

In `@tests/utils/page/actions.ts`:
- Around line 844-855: Update addDuplicateRow so its CodeMirror editor click
targets the left edge, matching addRowToActiveTab, before typing the duplicate
value. Preserve the existing duplicate-row selection and keyboard typing flow.

---

Outside diff comments:
In `@packages/bruno-app/src/components/RequestTabs/RequestTab/index.js`:
- Around line 257-281: Update both Save & Close handlers, including the
global-environment handler, to use the existing showSaveError callback when
their save promises reject. Replace the generic error toast handling while
preserving the current success behavior and duplicate-secret message.
🪄 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 Plus

Run ID: b84263fd-cecb-42a7-8215-f1a973fd1c07

📥 Commits

Reviewing files that changed from the base of the PR and between d57f859 and 8f6e529.

📒 Files selected for processing (14)
  • packages/bruno-app/src/components/EnvironmentVariablesTable/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-app/src/utils/environments.js
  • packages/bruno-app/src/utils/environments.spec.js
  • tests/environments/environment-tabs/environment-tabs.spec.ts
  • tests/utils/page/actions.ts
  • tests/utils/page/locators.ts
  • tests/utils/page/toast.ts

Comment on lines +181 to +207
await test.step('Editing the Variables tab marks only Variables unsaved', async () => {
await addRowToActiveTab(page, 'host', 'https://echo.usebruno.com');
await expect(variablesTabDot(page)).toBeVisible();
await expect(secretsTabDot(page)).toBeHidden();
await expect(tabCount(page, 'variables')).toHaveClass(/unsaved/);
await expect(tabCount(page, 'secrets')).toHaveCount(0);
});

await test.step('Editing the Secrets tab lights up its own dot without clearing Variables', async () => {
await test.step('Editing the Secrets tab marks it unsaved without clearing Variables', async () => {
await secretsTab(page).click();
await addRowToActiveTab(page, 'apiToken', 'super-secret-token-12345');
await expect(secretsTabDot(page)).toBeVisible();
// The Variables tab still has its unsaved row, so its dot must remain.
await expect(variablesTabDot(page)).toBeVisible();
await expect(tabCount(page, 'secrets')).toHaveClass(/unsaved/);
// The Variables tab still has its unsaved row, so its marker must remain.
await expect(tabCount(page, 'variables')).toHaveClass(/unsaved/);
});

await test.step('Saving the Secrets tab clears only the Secrets dot', async () => {
await test.step('Saving the Secrets tab clears only its unsaved marker', async () => {
await saveTab(page).click();
await expect(page.getByText('Changes saved successfully').last()).toBeVisible();
await expect(secretsTabDot(page)).toBeHidden();
await expect(variablesTabDot(page)).toBeVisible();
await expect(tabCount(page, 'secrets')).not.toHaveClass(/unsaved/);
await expect(tabCount(page, 'variables')).toHaveClass(/unsaved/);
});

await test.step('Saving the Variables tab clears the last remaining dot', async () => {
await test.step('Saving the Variables tab clears the last unsaved marker', async () => {
await variablesTab(page).click();
await saveTab(page).click();
await expect(page.getByText('Changes saved successfully').last()).toBeVisible();
await expect(variablesTabDot(page)).toBeHidden();
await expect(secretsTabDot(page)).toBeHidden();
await expect(tabCount(page, 'variables')).not.toHaveClass(/unsaved/);
await expect(tabCount(page, 'secrets')).not.toHaveClass(/unsaved/);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Assert the visible tab counts, not only their unsaved class.

These scenarios pass if the badge displays an incorrect count, such as a constant 1. Add count-text assertions after each add and after saves, while retaining the existing unsaved-state checks.

  • tests/environments/environment-tabs/environment-tabs.spec.ts#L181-L207: assert Variables and Secrets display the expected enabled-item counts and retain them after saving.
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L579-L605: add the same assertions for global environments.

As per coding guidelines, E2E tests must verify user-visible outcomes and cover new functionality.

📍 Affects 1 file
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L181-L207 (this comment)
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L579-L605
🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts` around lines
181 - 207, Update the environment-tab scenarios at
tests/environments/environment-tabs/environment-tabs.spec.ts:181-207 and
:579-605 to assert the visible Variables and Secrets tab-count text after each
row is added and again after saving, using the expected enabled-item counts.
Retain the existing unsaved-class assertions, and apply the same count-text
coverage to the global-environments scenario.

Sources: Coding guidelines, Path instructions

Comment thread tests/utils/page/actions.ts Outdated
Comment on lines +844 to +855
const addDuplicateRow = async (page: Page, name: string, value: string) => {
await test.step(`Add duplicate row "${name}" to the active environment tab`, async () => {
const env = buildCommonLocators(page).environment;
const nameInput = env.addRowNameInput();
await nameInput.waitFor({ state: 'visible' });
await nameInput.fill(name);
// Wait for the duplicate row to render before resolving `.last()`, else it can bind to the
// pre-existing twin and type the value into the wrong row.
await expect(env.varRow(name)).toHaveCount(2);
const editor = env.varRow(name).last().getByTestId(/^test-multiline-editor-\d+\.value$/).locator('.CodeMirror').first();
await editor.click();
await page.keyboard.type(value);

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file outline =="
ast-grep outline tests/utils/page/actions.ts --match addDuplicateRow --view expanded || true

echo
echo "== relevant excerpt =="
sed -n '820,870p' tests/utils/page/actions.ts

echo
echo "== search addRowToActiveTab and CodeMirror click patterns =="
rg -n "addRowToActiveTab|editor\.click|CodeMirror-focused|test-multiline-editor|scrollIntoViewIfNeeded|left-edge|left" tests -S

Repository: usebruno/bruno

Length of output: 15603


Click the duplicate editor at the left edge.

addRowToActiveTab targets the left edge of the CodeMirror editor to avoid the value-column data-type selector; addDuplicateRow should do the same before typing so the duplicate value is not intercepted.

Proposed fix
-    await editor.click();
+    await editor.scrollIntoViewIfNeeded();
+    await editor.click({ position: { x: 5, y: 5 } });
+    await expect(editor).toHaveClass(/CodeMirror-focused/);
     await page.keyboard.type(value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addDuplicateRow = async (page: Page, name: string, value: string) => {
await test.step(`Add duplicate row "${name}" to the active environment tab`, async () => {
const env = buildCommonLocators(page).environment;
const nameInput = env.addRowNameInput();
await nameInput.waitFor({ state: 'visible' });
await nameInput.fill(name);
// Wait for the duplicate row to render before resolving `.last()`, else it can bind to the
// pre-existing twin and type the value into the wrong row.
await expect(env.varRow(name)).toHaveCount(2);
const editor = env.varRow(name).last().getByTestId(/^test-multiline-editor-\d+\.value$/).locator('.CodeMirror').first();
await editor.click();
await page.keyboard.type(value);
const addDuplicateRow = async (page: Page, name: string, value: string) => {
await test.step(`Add duplicate row "${name}" to the active environment tab`, async () => {
const env = buildCommonLocators(page).environment;
const nameInput = env.addRowNameInput();
await nameInput.waitFor({ state: 'visible' });
await nameInput.fill(name);
// Wait for the duplicate row to render before resolving `.last()`, else it can bind to the
// pre-existing twin and type the value into the wrong row.
await expect(env.varRow(name)).toHaveCount(2);
const editor = env.varRow(name).last().getByTestId(/^test-multiline-editor-\d+\.value$/).locator('.CodeMirror').first();
await editor.scrollIntoViewIfNeeded();
await editor.click({ position: { x: 5, y: 5 } });
await expect(editor).toHaveClass(/CodeMirror-focused/);
await page.keyboard.type(value);
🤖 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 `@tests/utils/page/actions.ts` around lines 844 - 855, Update addDuplicateRow
so its CodeMirror editor click targets the left edge, matching
addRowToActiveTab, before typing the duplicate value. Preserve the existing
duplicate-row selection and keyboard typing flow.

@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: 2

🧹 Nitpick comments (1)
tests/environments/environment-tabs/environment-tabs.spec.ts (1)

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

Keep selectors in shared page locators.

  • tests/environments/environment-tabs/environment-tabs.spec.ts#L13-L13: move the row-name-input locator to tests/utils/page/locators.ts.
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L203-L211: use toastByMessage() for success-toast assertions.
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L654-L662: use toastByMessage() for success-toast assertions.

As per path instructions, specs should consume page-module locators and reusable toast locator builders.

🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts` at line 13, The
environment-tabs spec should use shared locators instead of defining or
asserting UI selectors locally: move varNameInput into
tests/utils/page/locators.ts, update its usages to consume the shared page
locator, and replace the success-toast assertions at
tests/environments/environment-tabs/environment-tabs.spec.ts lines 203-211 and
654-662 with toastByMessage().

Source: Path instructions

🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts`:
- Around line 444-448: Remove the negative success-toast assertions from both
affected test steps in
tests/environments/environment-tabs/environment-tabs.spec.ts at lines 444-448
and 892-896. Keep the saveEnvironment call and positive DUPLICATE_SECRET_TOAST
visibility assertion, and do not add other negative toast assertions.
- Around line 873-904: The existing global-environment coverage only verifies
duplicate secret rejection; add a Variables-tab case that creates duplicate
variable names and confirms saving succeeds. Anchor the test near “blocks saving
two secrets that share a name,” use the global environment setup and Variables
tab, and assert the successful-save toast without expecting duplicate-variable
errors.

---

Nitpick comments:
In `@tests/environments/environment-tabs/environment-tabs.spec.ts`:
- Line 13: The environment-tabs spec should use shared locators instead of
defining or asserting UI selectors locally: move varNameInput into
tests/utils/page/locators.ts, update its usages to consume the shared page
locator, and replace the success-toast assertions at
tests/environments/environment-tabs/environment-tabs.spec.ts lines 203-211 and
654-662 with toastByMessage().
🪄 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 Plus

Run ID: 71b6a738-033d-463b-bcf4-824e6c18486f

📥 Commits

Reviewing files that changed from the base of the PR and between 8f6e529 and 72de188.

📒 Files selected for processing (1)
  • tests/environments/environment-tabs/environment-tabs.spec.ts

Comment on lines +444 to +448
await test.step('Saving is blocked with an error and no success toast', async () => {
await saveEnvironment(page);
await expect(toastByMessage(page, DUPLICATE_SECRET_TOAST)).toBeVisible();
await expect(toastByMessage(page, 'Changes saved successfully')).toHaveCount(0);
});

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the negative success-toast assertions.

The duplicate-error toast is the expected positive outcome; asserting that another toast is absent violates the repository’s E2E convention. Based on learnings, do not add or recommend negative toast assertions.

📍 Affects 1 file
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L444-L448 (this comment)
  • tests/environments/environment-tabs/environment-tabs.spec.ts#L892-L896
🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts` around lines
444 - 448, Remove the negative success-toast assertions from both affected test
steps in tests/environments/environment-tabs/environment-tabs.spec.ts at lines
444-448 and 892-896. Keep the saveEnvironment call and positive
DUPLICATE_SECRET_TOAST visibility assertion, and do not add other negative toast
assertions.

Source: Learnings

Comment on lines +873 to +904

test('blocks saving two secrets that share a name', async ({ page, createTmpDir }) => {
await importCollection(page, collectionFile, await createTmpDir('global-secret-duplicate-name'), {
expectedCollectionName: 'test_collection'
});

await createEnvironment(page, 'Global Duplicate Secret Env', 'global');

await test.step('Add two secrets with the same name on the Secrets tab', async () => {
await secretsTab(page).click();
await addRowToActiveTab(page, 'apiToken', 'first-value');
// A second row reusing the name a duplicate is defined by its name, so only the name is filled.
await envLocators(page).addRowNameInput().fill('apiToken');
});

await test.step('The duplicate row is flagged inline', async () => {
await expect(varErrors(page)).toHaveCount(1);
});

await test.step('Saving is blocked with an error and no success toast', async () => {
await saveEnvironment(page);
await expect(toastByMessage(page, DUPLICATE_SECRET_TOAST)).toBeVisible();
await expect(toastByMessage(page, 'Changes saved successfully')).toHaveCount(0);
});

await test.step('Renaming the duplicate to a unique name clears the error and lets it save', async () => {
await varNameInput(page, 'apiToken').fill('apiTokenBackup');
await expect(varErrors(page)).toHaveCount(0);
await saveEnvironment(page);
await expect(toastByMessage(page, 'Changes saved successfully').last()).toBeVisible();
});
});

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Cover duplicate Variables in the global save flow.

Add a global-environment Variables-tab case where duplicate names save successfully. The PR covers global secret rejection, but not the requirement that variables remain unaffected in that separate environment type. As per coding guidelines, tests must cover successful behavior as well as realistic errors.

🤖 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 `@tests/environments/environment-tabs/environment-tabs.spec.ts` around lines
873 - 904, The existing global-environment coverage only verifies duplicate
secret rejection; add a Variables-tab case that creates duplicate variable names
and confirms saving succeeds. Anchor the test near “blocks saving two secrets
that share a name,” use the global environment setup and Variables tab, and
assert the successful-save toast without expecting duplicate-variable errors.

Source: Coding guidelines

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