#9081·formbricks

Access-Control-Allow-Credentials: true alongside Allow-Origin: * on /api/*/client and /api/capture

Author: tiagovilasboasCreated Aug 29, 2026Updated Sep 12, 2026
Labelssecurityagent-ready

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-Credentials at :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:15 and packages/surveys/src/lib/api-client.ts:135 both use the fetch default (same-origin). grep -rn "credentials:" packages/js-core/src packages/surveys/src packages/survey-ui/src apps/web/modules returns nothing.
  • **These routes carry no session. **apps/web/app/api/v1/client/ and apps/web/app/api/v2/client/ contain no cookies(), 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-Credentials is removed from the /api/(v1|v2)/client/:path* block in apps/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 returns Access-Control-Allow-Origin: * and **no **Access-Control-Allow-Credentials header.
  • 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).