CORS wildcard plus credentials true is not the permissive setting it looks like
Par Seaworthy · 18 août 2026
Photo by Jan Tinneberg on Unsplash
A common ask when setting up a new API: "the frontend keeps getting CORS errors, fix it." The fix an assistant reaches for is often this:
app.use(cors({
origin: (origin, callback) => callback(null, origin),
credentials: true,
}))
The errors go away. Every origin gets reflected back as allowed, and cookies and Authorization headers are permitted across origins. It works in the sense that the frontend can now talk to the API from anywhere, which is exactly the problem.
What credentials true actually removes
CORS exists to let a browser enforce that a page on evil.example.com can't read responses from api.yourcompany.com using the logged-in user's session cookie, even though the browser will happily send that cookie along with the request. The Access-Control-Allow-Origin header is what tells the browser whether the response is allowed to be read by the requesting page's JavaScript.
A literal wildcard, Access-Control-Allow-Origin: *, combined with Access-Control-Allow-Credentials: true, is actually rejected by browsers, specifically because that combination would be unsafe. But reflecting the request's Origin header back as the allowed origin, which is what the code above does, sidesteps that browser-level guard rail entirely. Every distinct origin gets its own explicit, valid allow-header in the response, so the browser sees a well-formed, specific origin match every time and permits the credentialed request. Functionally, this achieves exactly what the wildcard-plus-credentials combination is blocked from doing directly.
The practical result: a page hosted anywhere, on any domain, can make a request to your API using a logged-in visitor's session cookie, read the response, and exfiltrate whatever data or CSRF-protected action that session had access to. This is the exact cross-origin attack CORS was built to stop, reintroduced by a configuration that looks permissive rather than broken.
The fix is an explicit allowlist
const allowedOrigins = ['https://app.yourcompany.com', 'https://yourcompany.com']
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true,
}))
Now credentials: true is only ever paired with an explicitly approved origin, which is the combination CORS is designed to support safely. Local development can add http://localhost:3000 to the same array, or read the allowlist from an environment variable so staging and production don't share a config file.
If the API genuinely needs to be callable from arbitrary origins, for example a public, unauthenticated API, the fix is the opposite direction: drop credentials: true entirely and use a real wildcard. The dangerous combination is specifically wildcard-like behavior alongside credentials, not either one on its own.
It's worth remembering that CORS is a browser-enforced protection, not a server-side authorization layer. A tool like curl or a backend service calling your API directly ignores CORS headers entirely, since there's no browser in the loop to enforce them. Fixing the allowlist stops the browser-based cookie theft scenario, but it isn't a substitute for actual authentication and authorization checks on every endpoint.
Why the reflected-origin pattern shows up so often
"CORS error" is one of the more confusing error messages a developer will hit, because the browser reports it at the point of the failed fetch, with no indication of which server-side header is missing or wrong. Reflecting the origin back is the fastest way to make the error disappear for every origin at once, which matches what someone debugging a frontend integration is actually trying to do in the moment. It's a fix aimed at the symptom, generated by pattern-matching against "how do I stop getting CORS errors," not against "what access am I granting."
Testing this yourself is one curl command
You don't need a browser to see the problem. Send a request with an arbitrary Origin header and look at what comes back:
curl -i -H "Origin: https://evil.example.com" https://api.yourcompany.com/api/me
If the response includes Access-Control-Allow-Origin: https://evil.example.com and Access-Control-Allow-Credentials: true, the server just confirmed it will let a page hosted anywhere read this endpoint's response using a visitor's cookies. Against a correctly configured allowlist, an origin that isn't on the list simply won't appear in the response headers at all, and a browser enforcing CORS will block the page from reading the response even though the request itself still goes through.
What to check for
Search your CORS configuration for any origin callback that unconditionally reflects the request's origin, and check whether credentials: true is set alongside it. Seaworthy flags this exact combination, since it's a config setting rather than an application bug, and reads as "working" right up until someone builds a page that exploits it.
Cet article a été généré par IA et résume des sources publiquement disponibles.
Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.
Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.