Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
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 |
|
Hey @mdroidian — this is a proposed upgrade for the |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
| // Browser popup flow: relay via postMessage to opener | ||
| if (window.opener) { | ||
| try { | ||
| window.opener.postMessage(payload, "*"); |
There was a problem hiding this comment.
🔴 postMessage sends OAuth authorization code with wildcard target origin, enabling code theft
The postMessage on line 185 uses "*" as the target origin, which sends the sensitive OAuth authorization code to any window that happens to be window.opener, without verifying its origin.
Attack scenario: A malicious page calls window.open("https://accounts.google.com/o/oauth2/v2/auth?client_id=<ROAMJS_CLIENT_ID>&redirect_uri=<ROAMJS_REDIRECT>&..."). The user sees Google's legitimate consent screen for the RoamJS app and authenticates. Google redirects to oauth-success.html with the authorization code. Since window.opener is the attacker's page, postMessage(payload, "*") delivers the code to the attacker. The attacker can then exchange this code for tokens via the anonymous google-auth endpoint (src/components/GoogleOauthPanel.tsx:123-131 uses anonymous: true), gaining full access to the user's Google account with the granted scopes.
Recommended fix
The state parameter already contains the initiator's origin (encoded at GoogleOauthPanel.tsx:53). The decoded stateObj.origin should be used as the postMessage target origin instead of "*". While an attacker could encode their own origin in the state, this still provides defense-in-depth because the receiver at GoogleOauthPanel.tsx:228 validates the full state value matches what it originally generated (including the nonce), so a tampered state would be rejected by the legitimate receiver anyway. The key improvement is that the code is no longer broadcast to arbitrary origins.
Prompt for agents
In static/oauth-success.html, line 185, change the postMessage target origin from "*" to a specific origin extracted from the decoded state parameter. The state object (decoded earlier in the try/catch block at lines 170-178) contains an `origin` field set by the initiating page (see src/components/GoogleOauthPanel.tsx:53). Store `stateObj.origin` in a variable (e.g., `var targetOrigin`) alongside the existing `session` variable at line 175, defaulting to "*" if decoding fails. Then on line 185, use that variable instead of "*": `window.opener.postMessage(payload, targetOrigin)`. This prevents the authorization code from being delivered to an unexpected opener origin.
Was this helpful? React with 👍 or 👎 to provide feedback.
mdroidian
left a comment
There was a problem hiding this comment.
Thank you for the contribution!
Unfortunately, this PR is targeting the wrong repo for the change it describes.
The Google extension sends users to https://roamjs.com/oauth?auth=true from GoogleOauthPanel.tsx, but the actual callback page for /oauth is implemented in the website repo at oauth/page.tsx. Adding static/oauth-success.htmlhere inroamjs/google` does not replace the live page unless that file is separately ported into the website app and deployed from there.
There are also a couple of behavior differences from the current live implementation that should be preserved if this gets moved:
- The current page only posts messages to trusted origins, while this version uses postMessage(..., "*").
- The current page forwards the full auth params and handles desktop-session error propagation more completely; this version only relays { code, state } in popup flow.
Requesting changes so this can be moved to the correct repo (website) and reconciled with the existing callback/security behavior before merge and a loom video testing the full login flow on both the browser and app (if any logic is changed).
Summary
roamjs.com/oauth?auth=trueredirect page that users see after authorizing their Google accountDetails
The HTML file at
static/oauth-success.htmlis self-contained and ready to be hosted atroamjs.com/oauth. It handles:codeandstateback to the Roam window viapostMessage/oauth/sessionfor the extension to pollpr-for-the-droid.mp4
Preview
The page features:
Test plan
static/oauth-success.htmllocally to verify the visual design