Crew website chat trusts a caller-supplied origin
Imported from GitHub issue El-SaMa/oma#158 by @El-SaMa.
What is wrong
resolveWebsiteChatOrigin in apps/web/lib/crew/security.ts returns parentOrigin from the JSON body when the request has no Origin header, or when Origin is a portal host (oma.lanetti.fi, *.oma.lanetti.fi, localhost:3000).
The widget chat route (apps/web/app/api/crew/widget/[siteKey]/chat/route.ts) uses that value, and runWebsiteCrewChat checks it again with the same allow-list. The site key is public in the embed snippet. There is no signature that the request came from the customer site.
A caller can omit Origin and set parentOrigin to a site on that widget's allow-list. The chat then runs: Crew credits are spent, leads can be stored, and the reply is returned. No login is required.
Fix
Trust the browser Origin only. Do not accept parentOrigin from the body as proof of the visitor site. If the embed iframe must name its parent, sign that value (widget key or a short-lived token minted for the allowed origin) and reject anything else.
Found
Read-only review of origin/main, 8 Oct 2026. Not confirmed against the live server.