fix(oauth2): prevent code injection in OAuth2 callback handling - #8405
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds OAuth2 state generation and callback validation across browser and protocol-handler flows, surfaces OAuth2 failures in the response pane, and expands unit/E2E coverage with new Bruno fixtures and Electron state-validation tests. ChangesOAuth2 State Validation
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 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: 4
Caution
Some comments are outside the diff and canât be posted inline due to platform limitations.
â ïļ Outside diff range comments (1)
packages/bruno-electron/src/utils/oauth2.js (1)
331-338: ð Security & Privacy | ð Major | ⥠Quick winReserve
stateinstead of appending a duplicate parameter.If
authorizationUrloradditionalParameters.authorizationalready containsstate,append()sends duplicatestateparams. OAuth providers differ on duplicate handling, so the callback may echo a different value and fail validation. Set the generated state after custom params so it is the single canonical value.Proposed fix
- if (effectiveState) { - authorizationUrlWithQueryParams.searchParams.append('state', effectiveState); - } if (additionalParameters?.authorization?.length) { additionalParameters.authorization.forEach((param) => { if (param.enabled && param.name) { if (param.sendIn === 'queryparams') { authorizationUrlWithQueryParams.searchParams.append(param.name, param.value || ''); } } }); } + if (effectiveState) { + authorizationUrlWithQueryParams.searchParams.set('state', effectiveState); + }- if (effectiveState) { - authorizationUrlWithQueryParams.searchParams.append('state', effectiveState); - } if (additionalParameters?.authorization?.length) { additionalParameters.authorization.forEach((param) => { if (param.enabled && param.name) { if (param.sendIn === 'queryparams') { authorizationUrlWithQueryParams.searchParams.append(param.name, param.value || ''); } } }); } + if (effectiveState) { + authorizationUrlWithQueryParams.searchParams.set('state', effectiveState); + }Also applies to: 865-872
ðĪ 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/utils/oauth2.js` around lines 331 - 338, The OAuth2 URL builder in oauth2.js is appending a generated state value with searchParams.append, which can create duplicate state query parameters when authorizationUrl or additionalParameters.authorization already includes state. Update the authorization URL assembly logic so the generated state is applied last and as the single canonical value, replacing any existing state parameter instead of appending another. Make this change in the code path that builds authorizationUrlWithQueryParams and in the related section noted in the comment so both flows use the same state handling.
ðĪ 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/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js`:
- Around line 96-98: The OAuth2 action handlers are passing the raw result of
formatIpcError() straight into the UI, which can leave an object payload
displayed as an unreadable message. In the Oauth2ActionButtons component,
normalize the output from formatIpcError() to a string before using it in
toast.error(...) and showOauth2Error(...), and keep the fallback message when
the formatted value is not a usable string. Apply the same fix in both affected
handlers so the UI always receives a human-readable OAuth error.
In `@packages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.js`:
- Around line 110-121: Add a test in oauth2-protocol-handler.spec.js that covers
the hash-fragment provider error path handled by handleOauth2ProtocolUrl when
parsing implicit callbacks. Reuse the existing
registerOauth2AuthorizationRequest and assert that a URL like
bruno://oauth2/callback#error=access_denied rejects with the provider error
before state validation, with resolve untouched and reject receiving an
Authorization Failed message. Ensure the new case sits alongside the existing
error-response precedence test so both ?error= and `#error`= branches are covered.
In `@tests/auth/oauth2/oauth2-state-validation.spec.ts`:
- Around line 64-83: The callback capture helper in installCallbackCapture is
mutating Electronâs second-instance listener set by removing all listeners and
re-adding wrappers, which changes listener order and breaks any original .once()
behavior. Update it so it observes the bruno:// callback URL without replacing
Brunoâs existing listeners, preserving the original second-instance wiring while
still storing the captured URL in __brunoCapturedCallbackUrl.
- Around line 45-52: The callback-code parsing in fetchAuthCodeFromTestbench is
too restrictive because OAuth codes are opaque and may not be hex-only; update
the regex used to extract the code from the authorization response HTML so it
accepts a generic non-empty callback code shape instead of only [a-f0-9]+. Keep
the existing response and match assertions, but make the code extraction in
oauth2-state-validation.spec.ts provider-agnostic so the test still verifies the
returned bruno://app/oauth2/callback URL.
---
Outside diff comments:
In `@packages/bruno-electron/src/utils/oauth2.js`:
- Around line 331-338: The OAuth2 URL builder in oauth2.js is appending a
generated state value with searchParams.append, which can create duplicate state
query parameters when authorizationUrl or additionalParameters.authorization
already includes state. Update the authorization URL assembly logic so the
generated state is applied last and as the single canonical value, replacing any
existing state parameter instead of appending another. Make this change in the
code path that builds authorizationUrlWithQueryParams and in the related section
noted in the comment so both flows use the same state handling.
ðŠ 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: 0eb95a0c-2269-4f7d-85aa-05458f09c51f
ð Files selected for processing (13)
packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.jspackages/bruno-electron/src/ipc/network/authorize-user-in-system-browser.jspackages/bruno-electron/src/ipc/network/authorize-user-in-window.jspackages/bruno-electron/src/utils/oauth2-protocol-handler.jspackages/bruno-electron/src/utils/oauth2.jspackages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.jstests/auth/oauth2/fixtures/collection/Authorization Code.brutests/auth/oauth2/fixtures/collection/Implicit.brutests/auth/oauth2/fixtures/collection/User Supplied State.brutests/auth/oauth2/fixtures/collection/bruno.jsontests/auth/oauth2/fixtures/collection/environments/Local.brutests/auth/oauth2/init-user-data/preferences.jsontests/auth/oauth2/oauth2-state-validation.spec.ts
There was a problem hiding this comment.
ð§đ Nitpick comments (1)
tests/auth/oauth2/oauth2-state-validation.spec.ts (1)
219-224: ð Maintainability & Code Quality | ðĩ Trivial | ⥠Quick winRemove leftover commented-out
test.stepwrapper.Every sibling test wraps the "start the flow" phase in
test.step, but this one has it commented out while the body still runs unwrapped â looks like a debugging leftover.ð§đ Proposed fix
- // await test.step('start the authorization code flow', async () => { - await stubOpenExternal(app); - await installCallbackCapture(app); - await clickGetAccessToken(page, 'AuthCodeUserSuppliedState'); - await waitForAuthorizationStarted(app); - // }); + await test.step('start the authorization code flow', async () => { + await stubOpenExternal(app); + await installCallbackCapture(app); + await clickGetAccessToken(page, 'AuthCodeUserSuppliedState'); + await waitForAuthorizationStarted(app); + });ðĪ 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/auth/oauth2/oauth2-state-validation.spec.ts` around lines 219 - 224, Remove the leftover commented-out test.step wrapper around the authorization flow setup in the oauth2-state-validation test; the body currently runs unwrapped while the same phase in sibling tests uses test.step. Clean up the AuthCodeUserSuppliedState flow by deleting the commented wrapper and keeping the existing calls to stubOpenExternal, installCallbackCapture, clickGetAccessToken, and waitForAuthorizationStarted directly in the test body.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.
Nitpick comments:
In `@tests/auth/oauth2/oauth2-state-validation.spec.ts`:
- Around line 219-224: Remove the leftover commented-out test.step wrapper
around the authorization flow setup in the oauth2-state-validation test; the
body currently runs unwrapped while the same phase in sibling tests uses
test.step. Clean up the AuthCodeUserSuppliedState flow by deleting the commented
wrapper and keeping the existing calls to stubOpenExternal,
installCallbackCapture, clickGetAccessToken, and waitForAuthorizationStarted
directly in the test body.
âđïļ Review info
âïļ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2d65b069-cd41-4ed0-a127-73d792006a59
ð Files selected for processing (9)
packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.jspackages/bruno-electron/src/ipc/network/authorize-user-in-window.jspackages/bruno-electron/src/utils/oauth2-protocol-handler.jspackages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.jstests/auth/oauth2/fixtures/collection/AuthCodeUserSuppliedState.brutests/auth/oauth2/fixtures/collection/AuthorizationCode.brutests/auth/oauth2/fixtures/collection/AuthorizationImplicit.brutests/auth/oauth2/fixtures/collection/ImplicitUserSuppliedState.brutests/auth/oauth2/oauth2-state-validation.spec.ts
ðĪ Files with no reviewable changes (1)
- packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js
â Files skipped from review due to trivial changes (1)
- tests/auth/oauth2/fixtures/collection/ImplicitUserSuppliedState.bru
ð§ Files skipped from review as they are similar to previous changes (3)
- packages/bruno-electron/src/utils/oauth2-protocol-handler.js
- packages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.js
- packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
f21b06e to
69aad54
Compare
There was a problem hiding this comment.
ð§đ Nitpick comments (1)
packages/bruno-electron/src/utils/oauth2-protocol-handler.js (1)
101-103: ð Maintainability & Code Quality | ðĩ Trivial | ⥠Quick winKeep the rejection call on one line.
Line 101 splits a single-argument function call across multiple lines; this violates the JS style rule. As per coding guidelines, âNo newlines inside function parentheses.â
Proposed fix
- rejectOauth2AuthorizationRequest( - new Error('OAuth2 state mismatch') - ); + rejectOauth2AuthorizationRequest(new Error('OAuth2 state mismatch'));ðĪ 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/utils/oauth2-protocol-handler.js` around lines 101 - 103, The rejection call in rejectOauth2AuthorizationRequest should be kept on a single line to match the no-newlines-inside-parentheses style rule. Update the OAuth2 state mismatch branch in oauth2-protocol-handler.js so the new Error('OAuth2 state mismatch') argument is passed inline to rejectOauth2AuthorizationRequest without splitting the function call across multiple lines.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.
Nitpick comments:
In `@packages/bruno-electron/src/utils/oauth2-protocol-handler.js`:
- Around line 101-103: The rejection call in rejectOauth2AuthorizationRequest
should be kept on a single line to match the no-newlines-inside-parentheses
style rule. Update the OAuth2 state mismatch branch in
oauth2-protocol-handler.js so the new Error('OAuth2 state mismatch') argument is
passed inline to rejectOauth2AuthorizationRequest without splitting the function
call across multiple lines.
âđïļ Review info
âïļ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f208fb70-5963-462c-b788-aa2e5a9be4ca
ð Files selected for processing (14)
packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.jspackages/bruno-electron/src/ipc/network/authorize-user-in-system-browser.jspackages/bruno-electron/src/ipc/network/authorize-user-in-window.jspackages/bruno-electron/src/utils/oauth2-protocol-handler.jspackages/bruno-electron/src/utils/oauth2.jspackages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.jstests/auth/oauth2/fixtures/collection/AuthCodeUserSuppliedState.brutests/auth/oauth2/fixtures/collection/AuthorizationCode.brutests/auth/oauth2/fixtures/collection/AuthorizationImplicit.brutests/auth/oauth2/fixtures/collection/ImplicitUserSuppliedState.brutests/auth/oauth2/fixtures/collection/bruno.jsontests/auth/oauth2/fixtures/collection/environments/Local.brutests/auth/oauth2/init-user-data/preferences.jsontests/auth/oauth2/oauth2-state-validation.spec.ts
â Files skipped from review due to trivial changes (3)
- tests/auth/oauth2/fixtures/collection/environments/Local.bru
- tests/auth/oauth2/fixtures/collection/bruno.json
- tests/auth/oauth2/init-user-data/preferences.json
ð§ Files skipped from review as they are similar to previous changes (10)
- tests/auth/oauth2/fixtures/collection/AuthorizationImplicit.bru
- tests/auth/oauth2/fixtures/collection/ImplicitUserSuppliedState.bru
- packages/bruno-electron/src/ipc/network/authorize-user-in-system-browser.js
- tests/auth/oauth2/fixtures/collection/AuthorizationCode.bru
- tests/auth/oauth2/fixtures/collection/AuthCodeUserSuppliedState.bru
- packages/bruno-electron/src/ipc/network/authorize-user-in-window.js
- packages/bruno-app/src/components/RequestPane/Auth/OAuth2/Oauth2ActionButtons/index.js
- packages/bruno-electron/tests/utils/oauth2-protocol-handler.spec.js
- packages/bruno-electron/src/utils/oauth2.js
- tests/auth/oauth2/oauth2-state-validation.spec.ts
69aad54 to
edd2314
Compare
|
@lohit-bruno need your eyes here! |
1d1bad7 to
6ad0596
Compare
462e919 to
775bf5b
Compare
05bc8c6 to
0996326
Compare
0996326 to
119d948
Compare
a6ff525 to
08726d3
Compare
âĶtate handling logic
073dd9d to
d7bc83e
Compare
JIRA - https://usebruno.atlassian.net/browse/BRU-3546
Description
Bruno didn't validate the state returned on the OAuth2 callback, and sent none at all when the user left it blank â leaving auth flows open to CSRF / code injection.
Changes:
Always issue a state â random when unset, or a random nonce appended to the user's value so it can't be predicted/replayed.
Validate the returned state against the issued one and abort on mismatch, in both the embedded-window and system-browser.
Covers authorization code + implicit grants (query params and hash fragments).
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
statefor both authorization-code and implicit flows, with nonce-protected state handling.statematch/mismatch scenarios (query + hash) and introduced new OAuth2 state-related test fixtures.