Access-Control-Allow-Credentials: true alongside Allow-Origin: * on /api/*/client and /api/capture
Context
Reported publicly (GitHub #9081) from an external OWASP ASVS 5.0 (L1) static review at commit 37b8c69, flagged by the reporter as configuration clarity with no exploitable impact. Nothing was tested against our environments. Verified against the repo at that same commit — the observation is accurate.
Problem
apps/web/next.config.mjs sets Access-Control-Allow-Credentials: true next to Access-Control-Allow-Origin: * on two route blocks. Per the CORS spec browsers reject the wildcard origin whenever credentials are requested, so the pair cannot take effect as written: the Allow-Credentials header is inert. The wildcard itself is intentional (these are public, unauthenticated client endpoints), which is what makes the credentials header read as a leftover.
Root cause / where to look
apps/web/next.config.mjs:222-233— the/api/(v1|v2)/client/:path*block;Allow-Credentialsat:224,Allow-Origin: *at:225.apps/web/next.config.mjs:235-247— the/api/capture/:path*block; same pair at:238-239.
Two findings that settle the reporter's open question ("if some client flow does need credentials, the wildcard has to become validated origin reflection"):
- No credentialed flow exists on these routes. Neither first-party client SDK sets
credentials: "include"—packages/js-core/src/lib/common/api.ts:15andpackages/surveys/src/lib/api-client.ts:135both use thefetchdefault (same-origin).grep -rn "credentials:" packages/js-core/src packages/surveys/src packages/survey-ui/src apps/web/modulesreturns nothing. - **These routes carry no session. **
apps/web/app/api/v1/client/andapps/web/app/api/v2/client/contain nocookies(),getServerSession,auth.api.getSession, or session-cookie read. They are the public survey-display/response endpoints.
So the first of the reporter's two readings applies: Allow-Credentials can be dropped. Because no browser can honour the header as currently paired, removing it is a no-op for browser clients, and non-browser clients ignore CORS entirely — this is not a breaking change.
Separately, /api/capture/:path* (:236) matches no route that exists: there is no apps/web/app/api/capture/ directory, and the only occurrence of the string api/capture in the repo is that config line itself. The whole block looks dead, not just its credentials header.
These two blocks are the only Access-Control-Allow-Credentials occurrences in the repo.
Done when
-
Access-Control-Allow-Credentialsis removed from the/api/(v1|v2)/client/:path*block inapps/web/next.config.mjs;Access-Control-Allow-Origin: *and the other CORS headers on that block are unchanged. - The
/api/capture/:path*block is either deleted (no such route exists) or kept with a code comment recording why — not left as-is without a decision. - A request to a
/api/v1/client/...endpoint returnsAccess-Control-Allow-Origin: *and **no **Access-Control-Allow-Credentialsheader. - A survey still loads and submits a response end-to-end through the JS SDK from a third-party origin (the flow these headers exist for).
- Reply on GitHub #9081 with the outcome — the reporter offered a PR and asked to be told either way.
Not in scope
Origin reflection / an allowlist for these routes. The wildcard is deliberate for public capture endpoints; ASVS V3.4.2 is knowingly not applied here.
Verification level
Config change with no logic — manual header check plus the SDK smoke test above. No new E2E spec (apps/web/next.config.mjs has no unit-testable surface today and a header assertion would be churn).
Source: formbricks/formbricks