diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000000..5dbcf8a14c --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-05-15 - [Secure postMessage origins in OAuth2 flow] +**Vulnerability:** Use of `*` wildcard as target origin in `window.postMessage` during OAuth2 redirection, which could leak authorization codes to malicious windows. +**Learning:** The codebase has multiple places (frontend and backend) where `postMessage` is used to communicate the OAuth code back to the opener. +**Prevention:** Use `window.location.origin` on the client and `domainHelper.getPublicUrl` on the server to resolve the trusted frontend origin. Always extract only the origin (scheme + host + port) using `new URL(url).origin` to ensure strict matching by the browser. diff --git a/packages/react-ui/src/app/routes/redirect.tsx b/packages/react-ui/src/app/routes/redirect.tsx index 0d7ccb0ed7..74db8bfcd4 100644 --- a/packages/react-ui/src/app/routes/redirect.tsx +++ b/packages/react-ui/src/app/routes/redirect.tsx @@ -13,7 +13,7 @@ const RedirectPage: React.FC = React.memo(() => { { code: code, }, - '*', + window.location.origin, ); } }, [location.search]); diff --git a/packages/react-ui/src/lib/oauth2-utils.ts b/packages/react-ui/src/lib/oauth2-utils.ts index 7b9ca004e9..942ad7fa2f 100644 --- a/packages/react-ui/src/lib/oauth2-utils.ts +++ b/packages/react-ui/src/lib/oauth2-utils.ts @@ -74,9 +74,10 @@ function constructUrl(params: OAuth2PopupParams, pckeChallenge: string) { function getCode(redirectUrl: string): Promise { return new Promise((resolve) => { window.addEventListener('message', function handler(event) { + const expectedOrigin = new URL(redirectUrl).origin; if ( redirectUrl && - redirectUrl.startsWith(event.origin) && + event.origin === expectedOrigin && event.data['code'] ) { resolve(decodeURIComponent(event.data.code)); diff --git a/packages/server/api/src/app/app.ts b/packages/server/api/src/app/app.ts index 671220419f..bd60aa922b 100644 --- a/packages/server/api/src/app/app.ts +++ b/packages/server/api/src/app/app.ts @@ -250,12 +250,14 @@ export const setupApp = async (app: FastifyInstance): Promise = return reply.send('The code is missing in url') } else { + const frontendUrl = await domainHelper.getPublicUrl({ platformId: null }) + const frontendOrigin = new URL(frontendUrl).origin return reply .type('text/html') .send( ` Redirect succuesfully, this window should close now`, + )}' }, '${frontendOrigin}')} Redirect succuesfully, this window should close now`, ) } },