fix(secrets): block duplicate secret names to prevent value loss#8750
fix(secrets): block duplicate secret names to prevent value loss#8750pooja-bruno wants to merge 2 commits into
Conversation
WalkthroughDuplicate 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. ChangesEnvironment validation and indicators
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested reviewers: 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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winUse
showSaveErrorin 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
📒 Files selected for processing (14)
packages/bruno-app/src/components/EnvironmentVariablesTable/index.jspackages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.jspackages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.jspackages/bruno-app/src/components/RequestTabs/RequestTab/index.jspackages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.jspackages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.jspackages/bruno-app/src/providers/ReduxStore/slices/collections/actions.jspackages/bruno-app/src/providers/ReduxStore/slices/global-environments.jspackages/bruno-app/src/utils/environments.jspackages/bruno-app/src/utils/environments.spec.jstests/environments/environment-tabs/environment-tabs.spec.tstests/utils/page/actions.tstests/utils/page/locators.tstests/utils/page/toast.ts
| 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/); |
There was a problem hiding this comment.
📐 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
| 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); |
There was a problem hiding this comment.
🩺 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 -SRepository: 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.
| 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.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/environments/environment-tabs/environment-tabs.spec.ts (1)
13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep selectors in shared page locators.
tests/environments/environment-tabs/environment-tabs.spec.ts#L13-L13: move the row-name-input locator totests/utils/page/locators.ts.tests/environments/environment-tabs/environment-tabs.spec.ts#L203-L211: usetoastByMessage()for success-toast assertions.tests/environments/environment-tabs/environment-tabs.spec.ts#L654-L662: usetoastByMessage()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
📒 Files selected for processing (1)
tests/environments/environment-tabs/environment-tabs.spec.ts
| 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); | ||
| }); |
There was a problem hiding this comment.
📐 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
|
|
||
| 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(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
📐 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
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:
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
Bug Fixes
Tests
Screen.Recording.2026-07-23.at.6.16.36.PM.mov