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
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2025-05-15 - [HIGH] Insecure postMessage Origin in OAuth Redirects
**Vulnerability:** Use of wildcard origin (`'*'`) in `window.postMessage` calls within OAuth redirect handlers (both server-side generated HTML and client-side React routes).
**Learning:** Wildcard origins in `postMessage` allow any site that can open or iframe the redirect page to intercept sensitive information, such as OAuth authorization codes. In a multi-tenant environment (like Activepieces with custom domains), the expected origin must be resolved dynamically.
**Prevention:** Always restrict the `targetOrigin` to a trusted value. For client-side redirects, use `window.location.origin` if the communication is same-origin. For server-side generated redirects, use platform-aware URL resolvers (e.g., `domainHelper.getPublicUrl`) to determine the correct frontend origin.
2 changes: 1 addition & 1 deletion packages/react-ui/src/app/routes/redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const RedirectPage: React.FC = React.memo(() => {
{
code: code,
},
'*',
window.location.origin,
);
}
}, [location.search]);
Expand Down
10 changes: 9 additions & 1 deletion packages/react-ui/src/lib/oauth2-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,18 @@ function constructUrl(params: OAuth2PopupParams, pckeChallenge: string) {

function getCode(redirectUrl: string): Promise<string> {
return new Promise<string>((resolve) => {
let expectedOrigin: string | null = null;
try {
expectedOrigin = redirectUrl ? new URL(redirectUrl).origin : null;
} catch (e) {
console.error('Invalid redirectUrl', redirectUrl, e);
}

window.addEventListener('message', function handler(event) {
if (
redirectUrl &&
redirectUrl.startsWith(event.origin) &&
expectedOrigin &&
event.origin === expectedOrigin &&
event.data['code']
) {
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 frontendUrl = await domainHelper.getPublicUrl({ platformId })
const targetOrigin = new URL(frontendUrl).origin
Comment on lines +254 to +256
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 for /redirect responses

Deriving targetOrigin from domainHelper.getPublicUrl({ platformId }) here can send the OAuth code to the wrong origin in cloud custom-domain setups that use AP_INTERNAL_URL: THIRD_PARTY_AUTH_PROVIDER_REDIRECT_URL is generated via getInternalUrl (shared/internal host), so this /redirect request may not map to a tenant host, getPlatformIdForRequest falls back to null, and the handler posts to the default frontend origin instead of the tenant opener origin. In that environment, window.opener.postMessage is blocked by origin mismatch and the OAuth/SSO popup never returns a code.

Useful? React with 👍 / 👎.

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 thread
coderabbitai[bot] marked this conversation as resolved.
)
}
},
Expand Down
Loading