Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 2025-05-14 - [Insecure postMessage Target Origin in OAuth Redirect]
**Vulnerability:** The OAuth `/redirect` endpoint used `window.postMessage` with a wildcard (`'*'`) target origin, allowing any origin to intercept the authorization code.
**Learning:** In a multi-platform environment like Activepieces, the correct target origin must be resolved dynamically using the platform context to ensure the code is only sent to the trusted frontend.
**Prevention:** Always use `platformUtils.getPlatformIdForRequest(request)` and `domainHelper.getPublicUrl({ platformId })` to resolve the specific origin for `postMessage`.

## 2025-05-14 - [Improper Origin Verification in Frontend OAuth Utility]
**Vulnerability:** The frontend `getCode` function used `startsWith` to verify the origin of `postMessage` events, which could be bypassed (e.g., `https://trusted.com.malicious.com` starts with `https://trusted.com`).
**Learning:** `startsWith` is insufficient for origin verification.
**Prevention:** Always use strict equality (`===`) for origin comparison and extract the origin component correctly using `new URL(url).origin`.
3 changes: 2 additions & 1 deletion packages/react-ui/src/lib/oauth2-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ function constructUrl(params: OAuth2PopupParams, pckeChallenge: string) {
}

function getCode(redirectUrl: string): Promise<string> {
const expectedOrigin = new URL(redirectUrl).origin;
return new Promise<string>((resolve) => {
window.addEventListener('message', function handler(event) {
if (
redirectUrl &&
redirectUrl.startsWith(event.origin) &&
event.origin === expectedOrigin &&
event.data['code']
) {
Comment on lines 77 to 82
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=ts -C3 "addEventListener\\('message'" packages/react-ui/src/lib/oauth2-utils.ts
rg -n --type=ts -C3 "event\\.source\\s*===\\s*currentPopup|currentPopup\\s*===\\s*event\\.source" packages/react-ui/src/lib/oauth2-utils.ts

Repository: AGI-Corporation/Route.X

Length of output: 384


🏁 Script executed:

#!/bin/bash
# Read the full context around the handler
sed -n '70,95p' packages/react-ui/src/lib/oauth2-utils.ts

# Search for currentPopup declaration and usage in the file
rg -n "currentPopup" packages/react-ui/src/lib/oauth2-utils.ts

Repository: AGI-Corporation/Route.X

Length of output: 889


🏁 Script executed:

#!/bin/bash
# Check if there are other message listeners in the file
rg -n "addEventListener.*message" packages/react-ui/src/lib/oauth2-utils.ts

# Look at the openWindow function to understand the popup context
rg -n -B2 -A8 "function openWindow" packages/react-ui/src/lib/oauth2-utils.ts

# Check the full file to see the complete flow
wc -l packages/react-ui/src/lib/oauth2-utils.ts

Repository: AGI-Corporation/Route.X

Length of output: 415


Bind OAuth message acceptance to the popup window (event.source) as well as origin.

event.origin === expectedOrigin is necessary, but not sufficient for popup integrity. A different same-origin window can still post a forged { code } message and satisfy this check.

πŸ” Proposed hardening
 function getCode(redirectUrl: string): Promise<string> {
   const expectedOrigin = new URL(redirectUrl).origin;
   return new Promise<string>((resolve) => {
     window.addEventListener('message', function handler(event) {
       if (
-        redirectUrl &&
+        event.source === currentPopup &&
         event.origin === expectedOrigin &&
-        event.data['code']
+        typeof event.data === 'object' &&
+        event.data !== null &&
+        'code' in event.data &&
+        typeof (event.data as { code?: unknown }).code === 'string'
       ) {
-        resolve(decodeURIComponent(event.data.code));
+        resolve(decodeURIComponent((event.data as { code: string }).code));
         currentPopup?.close();
         window.removeEventListener('message', handler);
       }
     });
   });
 }
πŸ€– 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 77 - 82, The message
handler currently only checks origin and redirectUrl which is vulnerable to
same-origin forged posts; update the handler in oauth2-utils.ts to also verify
event.source matches the popup window reference you opened (e.g. the auth popup
variable used when launching OAuth) before accepting event.data['code'], and
ensure you remove the listener after success; reference the existing handler
function, event, expectedOrigin, and redirectUrl when implementing the
event.source === popupWindowRef check.

resolve(decodeURIComponent(event.data.code));
Expand Down
6 changes: 5 additions & 1 deletion packages/server/api/src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import { pieceMetadataServiceHooks } from './pieces/piece-metadata-service/hooks
import { pieceSyncService } from './pieces/piece-sync-service'
import { platformModule } from './platform/platform.module'
import { platformService } from './platform/platform.service'
import { platformUtils } from './platform/platform.utils'
import { projectHooks } from './project/project-hooks'
import { projectModule } from './project/project-module'
import { storeEntryModule } from './store-entry/store-entry.module'
Expand Down Expand Up @@ -250,12 +251,15 @@ export const setupApp = async (app: FastifyInstance): Promise<FastifyInstance> =
return reply.send('The code is missing in url')
}
else {
const platformId = await platformUtils.getPlatformIdForRequest(request)
const publicUrl = await domainHelper.getPublicUrl({ platformId })
const targetOrigin = new URL(publicUrl).origin
return reply
.type('text/html')
.send(
`<script>if(window.opener){window.opener.postMessage({ 'code': '${encodeURIComponent(
params.code,
)}' },'*')}</script> <html>Redirect succuesfully, this window should close now</html>`,
)}' }, '${targetOrigin}')}</script> <html>Redirect successfully, this window should close now</html>`,
Comment on lines +256 to +262
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 Preserve opener origin when posting OAuth code

The /redirect handler now hard-codes postMessage to the platform public origin, which can differ from the actual opener origin for valid deployments (for example, a platform with a custom domain where users are still on the default cloud host, or any alternate frontend host behind the same backend). In those cases the browser drops the message because targetOrigin does not match window.opener’s real origin, so OAuth popups never resolve and connection/login flows hang.

Useful? React with πŸ‘Β / πŸ‘Ž.

)
}
},
Expand Down
Loading