Skip to content

🛡️ Sentinel: Secure OAuth code transmission and fix origin verification#13

Open
AGI-Corporation wants to merge 2 commits intomainfrom
sentinel/secure-oauth-postmessage-1346839949802919774
Open

🛡️ Sentinel: Secure OAuth code transmission and fix origin verification#13
AGI-Corporation wants to merge 2 commits intomainfrom
sentinel/secure-oauth-postmessage-1346839949802919774

Conversation

@AGI-Corporation
Copy link
Copy Markdown
Owner

@AGI-Corporation AGI-Corporation commented Mar 9, 2026

🛡️ Sentinel Security Fix

🚨 Severity: HIGH
💡 Vulnerability: Insecure OAuth code transmission via postMessage.
🎯 Impact: An attacker could intercept sensitive OAuth codes if they opened the redirect URL in a popup, potentially leading to account takeover. The frontend also used an insecure startsWith check for origin verification, which is susceptible to bypass.
🔧 Fix:

  1. Resolved the platform-specific frontend origin in the backend and used it as the targetOrigin for postMessage.
  2. Updated the frontend to use strict equality (===) for origin verification.
  3. Optimized the frontend check and fixed a typo in the success message.
    ✅ Verification: Targeted linting with ESLint passed for modified files. Verification of origin resolution logic via existing platformUtils and domainHelper.

PR created automatically by Jules for task 1346839949802919774 started by @AGI-Corporation

Summary by CodeRabbit

  • Documentation

    • Added security notice documenting OAuth vulnerabilities and prevention measures.
  • Bug Fixes

    • Enhanced OAuth2 security by implementing stricter origin verification for sensitive authentication code transmission. This ensures OAuth codes are only transmitted to verified, trusted origins, preventing potential exposure through insecure cross-origin communication.

…ification

- Replace insecure '*' wildcard in postMessage target origin with resolved platform origin in /api/redirect.
- Upgrade frontend origin validation from startsWith to strict equality in oauth2-utils.
- Optimize origin check by moving calculation outside the listener.
- Correct typo in redirect success message.
- Add security learning to Sentinel journal.

Co-authored-by: AGI-Corporation <186229839+AGI-Corporation@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8bfd17f-562a-4582-8977-ce67fca3f7d6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request addresses a security vulnerability in OAuth code transmission via postMessage. The changes implement server-side dynamic resolution of the frontend origin and strengthen client-side origin verification from loose pattern matching to strict equality checks.

Changes

Cohort / File(s) Summary
Security Documentation
.jules/sentinel.md
Added documentation describing the OAuth2 code exposure vulnerability via postMessage with wildcard origins and the corresponding fixes required for both server and frontend.
Client-side Origin Verification
packages/react-ui/src/lib/oauth2-utils.ts
Strengthened the origin validation in the message handler from a loose startsWith check to exact equality comparison using event.origin === expectedOrigin.
Server-side Origin Resolution
packages/server/api/src/app/app.ts
Implemented dynamic runtime resolution of the redirect target origin by computing the platformId and deriving the frontend origin, replacing the wildcard origin with the computed targetOrigin in the postMessage call.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A code once traveled far and wide,
With postMessage as its guide,
But wildcards? No, too loose a net!
Now origins match with strict regret,
Security locked, no more threat!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main security fix: addressing insecure OAuth code transmission via postMessage and fixing origin verification, which aligns with the primary changes across all modified files.
Description check ✅ Passed The PR description addresses the template requirements but is incomplete. It explains what the PR does and the vulnerability fix, but lacks the 'Explain How the Feature Works' section with optional video and the 'Relevant User Scenarios' section.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sentinel/secure-oauth-postmessage-1346839949802919774

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 and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c531dbe6ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +253 to +255
const platformId = await platformUtils.getPlatformIdForRequest(request)
const frontendUrl = await domainHelper.getPublicUrl({ platformId })
const targetOrigin = new URL(frontendUrl).origin
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve postMessage target from redirect URI, not request host

targetOrigin is computed from platformUtils.getPlatformIdForRequest(request) and domainHelper.getPublicUrl(...), but the OAuth redirect URL is issued via federatedAuthnService.getThirdPartyRedirectUrl using domainHelper.getInternalUrl (which prefers INTERNAL_URL). When INTERNAL_URL is set, /redirect arrives on a shared host without tenant context, so getPlatformIdForRequest can return null and getPublicUrl falls back to the global frontend origin; for custom-domain tenants this origin differs from the popup opener, so window.opener.postMessage is discarded and the OAuth code never reaches the UI.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/react-ui/src/lib/oauth2-utils.ts (1)

74-88: ⚠️ Potential issue | 🔴 Critical

Origin mismatch breaks CLOUD_OAUTH2 flows silently.

The /redirect endpoint in app.ts (lines 240-262) computes targetOrigin from domainHelper.getPublicUrl({ platformId }), which resolves to either a custom domain or the configured FRONTEND_URL. It then sends a postMessage containing the OAuth code to this computed origin.

However, for CLOUD_OAUTH2 connections, the frontend hardcodes redirectUrl to 'https://secrets.activepieces.com/redirect' (oauth2-connection-settings.tsx, line 98). This causes getCode() to listen for postMessage from 'https://secrets.activepieces.com'.

When the OAuth provider redirects to the server's /redirect endpoint, the server posts the code to the actual frontend domain, not secrets.activepieces.com. The browser rejects this postMessage because the origin does not match the listener's expectation, causing the OAuth flow to hang indefinitely without error feedback.

This affects all OAuth flow types, as they all call openPopup() which invokes getCode() regardless of connection type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-ui/src/lib/oauth2-utils.ts` around lines 74 - 88, getCode
currently only accepts postMessage events whose origin equals expectedOrigin,
which breaks CLOUD_OAUTH2 when the server posts to a different frontend domain;
update getCode to still validate event.data but not rely solely on event.origin:
inside the message handler (function handler in getCode) keep the existing
expectedOrigin check but also accept messages where event.data.code exists and
either event.data.redirectUrl === redirectUrl or event.data.source ===
'oauth-redirect' (or another agreed-upon marker), then
resolve(decodeURIComponent(event.data.code)), close currentPopup, and remove the
listener; this preserves security by validating message payload (code +
redirectUrl/source) while fixing cross-origin redirects for openPopup flows.
🧹 Nitpick comments (2)
packages/server/api/src/app/app.ts (1)

258-261: Consider escaping interpolated values in the HTML template.

While targetOrigin comes from trusted sources (custom domain or FRONTEND_URL env var), interpolating it directly into a <script> tag without escaping is a defense-in-depth concern. If a malicious value were ever stored as a custom domain, it could potentially break out of the string context.

Similarly, params.code is URL-encoded which helps, but the single quotes around the template string could still be vulnerable to certain payloads.

🛡️ Optional: Use JSON.stringify for safer interpolation
                 return reply
                     .type('text/html')
                     .send(
-                        `<script>if(window.opener){window.opener.postMessage({ 'code': '${encodeURIComponent(
-                            params.code,
-                        )}' }, '${targetOrigin}')}</script> <html>Redirect successfully, this window should close now</html>`,
+                        `<script>if(window.opener){window.opener.postMessage({ 'code': ${JSON.stringify(encodeURIComponent(
+                            params.code,
+                        ))} }, ${JSON.stringify(targetOrigin)})}</script> <html>Redirect successfully, this window should close now</html>`,
                     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/api/src/app/app.ts` around lines 258 - 261, The HTML response
embeds unescaped values into a script string (the send(...) that writes
window.opener.postMessage with targetOrigin and
encodeURIComponent(params.code)); to harden this, wrap the interpolated values
using a safe serializer like JSON.stringify before inserting them into the
inline script (e.g., serialize targetOrigin and the code value passed to
postMessage) so the string context cannot be broken out of; update the send(...)
call to use the JSON-stringified versions of targetOrigin and params.code (or an
equivalent escape function) and ensure you still URL-encode params.code where
needed.
packages/react-ui/src/lib/oauth2-utils.ts (1)

78-81: Minor: Redundant redirectUrl truthiness check.

The check redirectUrl && on line 79 is unnecessary since redirectUrl was already used to construct expectedOrigin on line 76. If redirectUrl were falsy, new URL(redirectUrl) would have thrown an error before reaching this point.

♻️ Suggested simplification
     window.addEventListener('message', function handler(event) {
       if (
-        redirectUrl &&
         event.origin === expectedOrigin &&
         event.data['code']
       ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-ui/src/lib/oauth2-utils.ts` around lines 78 - 81, Remove the
redundant truthiness check for redirectUrl in the conditional that inspects the
postMessage event: when evaluating if (redirectUrl && event.origin ===
expectedOrigin && event.data['code']) drop the leading "redirectUrl &&" since
expectedOrigin is already derived from new URL(redirectUrl) earlier; update the
conditional to only check event.origin === expectedOrigin and event.data['code']
(preserving the same variable names event, expectedOrigin, and
event.data['code']) so behavior remains identical but the check is simplified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/react-ui/src/lib/oauth2-utils.ts`:
- Around line 74-88: getCode currently only accepts postMessage events whose
origin equals expectedOrigin, which breaks CLOUD_OAUTH2 when the server posts to
a different frontend domain; update getCode to still validate event.data but not
rely solely on event.origin: inside the message handler (function handler in
getCode) keep the existing expectedOrigin check but also accept messages where
event.data.code exists and either event.data.redirectUrl === redirectUrl or
event.data.source === 'oauth-redirect' (or another agreed-upon marker), then
resolve(decodeURIComponent(event.data.code)), close currentPopup, and remove the
listener; this preserves security by validating message payload (code +
redirectUrl/source) while fixing cross-origin redirects for openPopup flows.

---

Nitpick comments:
In `@packages/react-ui/src/lib/oauth2-utils.ts`:
- Around line 78-81: Remove the redundant truthiness check for redirectUrl in
the conditional that inspects the postMessage event: when evaluating if
(redirectUrl && event.origin === expectedOrigin && event.data['code']) drop the
leading "redirectUrl &&" since expectedOrigin is already derived from new
URL(redirectUrl) earlier; update the conditional to only check event.origin ===
expectedOrigin and event.data['code'] (preserving the same variable names event,
expectedOrigin, and event.data['code']) so behavior remains identical but the
check is simplified.

In `@packages/server/api/src/app/app.ts`:
- Around line 258-261: The HTML response embeds unescaped values into a script
string (the send(...) that writes window.opener.postMessage with targetOrigin
and encodeURIComponent(params.code)); to harden this, wrap the interpolated
values using a safe serializer like JSON.stringify before inserting them into
the inline script (e.g., serialize targetOrigin and the code value passed to
postMessage) so the string context cannot be broken out of; update the send(...)
call to use the JSON-stringified versions of targetOrigin and params.code (or an
equivalent escape function) and ensure you still URL-encode params.code where
needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da8e12cb-cf36-4f5c-b885-6068bb40e641

📥 Commits

Reviewing files that changed from the base of the PR and between f61e720 and c531dbe.

📒 Files selected for processing (3)
  • .jules/sentinel.md
  • packages/react-ui/src/lib/oauth2-utils.ts
  • packages/server/api/src/app/app.ts

…ification

- Replace insecure '*' wildcard in postMessage target origin with resolved platform origin in /api/redirect.
- Upgrade frontend origin validation from startsWith to strict equality in oauth2-utils.
- Optimize origin check by moving calculation outside the listener.
- Correct typo in redirect success message.
- Add security learning to Sentinel journal.

Co-authored-by: AGI-Corporation <186229839+AGI-Corporation@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant