-
Notifications
You must be signed in to change notification settings - Fork 0
🛡️ Sentinel: Secure OAuth2 redirect postMessage and origin verification #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0909895
cea2e1c
fc135c6
3999cad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## 2026-03-26 - [Secure OAuth Redirect PostMessage] | ||
| **Vulnerability:** Insecure cross-document communication using `window.postMessage` with wildcard target origin (`'*'`) and loose origin verification (`startsWith`) on the receiver side. | ||
| **Learning:** OAuth2 callback flows often use `postMessage` to transmit authorization codes from a temporary redirect window back to the main application. Using a wildcard target origin allows any origin (including malicious ones) to intercept the code if they can position themselves as the window opener. Similarly, loose origin verification on the receiver side (using `startsWith`) can be bypassed (e.g., `https://trusted.com.malicious.com`). | ||
| **Prevention:** Always use a specific `targetOrigin` when calling `postMessage`. In multi-tenant environments, resolve the trusted frontend origin dynamically (e.g., via `domainHelper` and platform context). On the receiver side, perform strict equality checks on `event.origin` against a trusted, pre-configured origin. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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' | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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 frontendUrl = await domainHelper.getPublicUrl({ platformId }) | ||||||||||||||||||||||||||||||||||||||||||||||
| const targetOrigin = new URL(frontendUrl).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 succuesfully, this window should close now</html>`, | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+254
to
+262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent failure when platform cannot be determined for custom domain requests. When Consider logging a warning when Suggested improvement const platformId = await platformUtils.getPlatformIdForRequest(request)
+if (platformId === null) {
+ request.log.warn({ host: request.headers.host }, 'Could not determine platform for OAuth redirect, falling back to default frontend URL')
+}
const frontendUrl = await domainHelper.getPublicUrl({ platformId })
const targetOrigin = new URL(frontendUrl).origin📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This computes
targetOriginfrom the/redirectrequest context, which breaks OAuth popups when the callback URL is served fromINTERNAL_URLbut the opener is on a tenant/custom frontend domain. In that configuration,federatedAuthnService.getThirdPartyRedirectUrlreturnsINTERNAL_URL/redirectanddomainHelper.getInternalUrlignoresplatformId, sogetPlatformIdForRequeston this route cannot recover the tenant andgetPublicUrl({ platformId: null })falls back to the globalFRONTEND_URL; the subsequentpostMessageis sent to the wrong origin and the code is never delivered to the opener. This regression only appears in deployments with differing internal/public domains, but it causes OAuth login/connection flows to hang for affected tenants.Useful? React with 👍 / 👎.