fix(auth): stop reflecting arbitrary origins in credentialed CORS - #5127
Merged
Conversation
The global CORS middleware reflected the incoming Origin header for every request while credentials:true was set, so any external site could issue a cookie-authenticated cross-site fetch against the API and read the response. The localhost/127.0.0.1 allowance was also a loose substring check bypassable by a hostname like "localhost.evil.com". resolveCorsOrigin now only reflects localhost/127.0.0.1 (exact hostname match) and the deployment's own origin (baseUrl setting, falling back to the request's own origin when unconfigured — the same fallback already used for redirect_uri validation in this file).
decocms Bot
pushed a commit
that referenced
this pull request
Jul 23, 2026
PR: #5127 fix(auth): stop reflecting arbitrary origins in credentialed CORS Bump type: patch - decocms (apps/mesh/package.json): 4.111.0 -> 4.111.1 Deploy-Scope: server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3757 ("Gateway CORS reflects any origin with credentials enabled").
The global CORS middleware in
apps/mesh/src/api/app.tsreflected the incomingOriginheader for every request (theorigincallback had a literal// TODO: Configure allowed origins from environmentand then returned the origin unconditionally), whilecredentials: truewas set. That combination lets any external website issue a cookie-authenticated cross-sitefetch(..., {credentials: 'include'})against the API and read the JSON response for a logged-in victim — a classic permissive-CORS-with-credentials hole (CWE-942). The existinglocalhost/127.0.0.1allowance was also a loose substring check (origin.includes("localhost")), bypassable by a hostname likehttp://localhost.evil.com.A maintainer wants this fixed because it's a real, currently-exploitable cross-tenant data-leak vector on every session-authenticated API route (chat, connections, org settings, etc.) — not just OAuth-proxy, but the same middleware also guards the OAuth/auth-adjacent routes I was auditing.
The fix extracts the origin decision into a small pure function,
resolveCorsOrigin(origin, { baseUrl, requestOrigin }), unit-tested the same wayisSsoExemptPathalready is in this file:localhost/127.0.0.1only on an exact hostname match (not substring).baseUrlorigin, falling back to the request's own origin whenbaseUrlisn't configured — mirroring the exact fallback pattern (getSettings().baseUrl ?? reqUrl.origin) already used a few dozen lines above forredirect_urivalidation in the same file.null(noAccess-Control-Allow-Originheader), which only blocks genuinely cross-origin browser reads — same-origin app traffic (the normal case, since the Studio client and API are served from the same origin in production) is unaffected because browsers don't gate same-origin requests on CORS headers at all.Net change:
apps/mesh/src/api/app.ts(+~40/-9),apps/mesh/src/api/app.test.ts(+6 test cases covering the localhost exact-match, the substring bypass, baseUrl reflection, the no-baseUrl fallback, and rejection of an arbitrary origin).To verify:
bun test apps/mesh/src/api/app.test.ts(10/10 pass, including the 6 newresolveCorsOrigincases).Locally ran:
bun run fmt(clean) andbunx tsc --noEmitinapps/mesh(no errors introduced by this change — the only errors present are pre-existing, in an unrelated untracked file). Full CI validates the rest.Summary by cubic
Stops reflecting arbitrary origins when CORS uses credentials, closing a data leak where any site could read cookie-authenticated API responses. Only trusted origins are allowed; same-origin production traffic is unaffected.
resolveCorsOriginto gate CORSoriginwhencredentials: true.localhost/127.0.0.1(any port) and the deployment origin fromgetSettings().baseUrl(fallback to the request’s origin if unset).localhost.evil.com, arbitrary sites, and malformed origins.resolveCorsOriginand added targeted tests inapps/mesh/src/api/app.test.ts.Written for commit 92e3d99. Summary will update on new commits.