CORS Misconfiguration
Also known as: Permissive CORS, Reflected Origin, Cross-origin resource sharing misconfiguration, Access-Control-Allow-Origin misconfiguration
A web security misconfiguration in which an API tells browsers that any site may read its responses, often by echoing the request's Origin header while also allowing credentials, so a hostile page can read a signed-in user's private data.
How it works
Cross-origin resource sharing is a set of HTTP headers that lets a server tell the browser which other sites may read its responses. By default the same-origin policy stops a page from one site reading data returned by another. CORS is a deliberate, server-controlled exception, and a misconfiguration is the server granting that exception to sites it should never trust.
The risky pattern is a server that copies the request's Origin header straight into Access-Control-Allow-Origin and also sends Access-Control-Allow-Credentials: true. Together these tell the browser that any site may make a request carrying the user's cookies and then read the reply. A hostile page visited by a signed-in user could therefore read account data, tokens or personal details the API returns, using the victim's own session.
Other weak variants include trusting the literal value null, matching origins with a loose suffix or substring test so that a look-alike domain passes, and trusting every subdomain when one of them is vulnerable. Browsers refuse the wildcard together with credentials, which is why developers reach for reflection as a workaround, and that workaround is exactly the flaw.
The defence is to decide trust on the server. Keep an allowlist of exact origins, return a matching origin only when the request's Origin is in that list, add Vary: Origin so caches do not mix responses, and restrict methods and headers to what the application needs. CORS is not authentication: sensitive endpoints still need their own access control.
Walk through it
- 1Read the CORS config
- 2Name the dangerous header
- 3Choose the trust model
- Scope the exposed endpoints
- Implement the allowlist and verify
A pre-release review flags the orders API. Its cross-origin middleware was written so that the web app and a partner portal could both call it. Read the config as a reviewer and note how it decides which origin to allow.
1// Applied to every route under /api2app.use((req, res, next) => {3 const origin = req.get("Origin");4 res.set("Access-Control-Allow-Origin", origin ?? "*");5 res.set("Access-Control-Allow-Credentials", "true");6 res.set("Access-Control-Allow-Methods", "*");7 next();8});Spot it
- Responses whose Access-Control-Allow-Origin equals the request's Origin for many different, unrelated origins.
- Access-Control-Allow-Credentials: true appearing together with a reflected or wildcard origin.
- Access-Control-Allow-Origin: null or origins that only loosely resemble the real domain in a response.
- Cross-origin requests to authenticated endpoints from domains the organisation does not own.
- A configuration review or scanner finding for permissive CORS on API routes that return personal data.
API gateway access log
ts=2026-10-11T10:02:11Z method=GET path=/api/orders/me status=200 origin=https://unrelated-site.example acao=https://unrelated-site.example acac=true
ts=2026-10-11T10:02:49Z method=GET path=/api/orders/me status=200 origin=https://app.contoso-orders.example acao=https://app.contoso-orders.example acac=true
ts=2026-10-11T10:03:05Z method=GET path=/api/profile status=200 origin=null acao=null acac=truesplSplunk: credentialed responses allowed for unapproved origins
index=gateway sourcetype=access acac=true
| where isnotnull(origin) AND NOT match(origin, "^https://(app|partners)\.contoso-orders\.example$")
| stats count by origin, acao, pathReplace the approved-origin pattern with your real allowlist; every hit is a candidate finding.
grepgrep: reflected origin with credentials in gateway logs
grep 'acac=true' gateway.log | grep -v 'origin=https://app.contoso-orders.example'Stop it
Match Origin against an exact server-side allowlist
Keep a fixed list of approved origins, compare the full scheme, host and port, and return Access-Control-Allow-Origin only when the request's Origin is on the list. Never copy the Origin header back unchecked.
Never combine a wildcard or reflected origin with credentials
Only an approved, specific origin may be paired with Access-Control-Allow-Credentials: true. Do not trust null, and do not use substring, prefix or suffix matching that a look-alike domain could satisfy.
Minimise scope and keep real access control
Allow only the methods and headers the application needs, add Vary: Origin, expose CORS only on the routes that need it, and remember that CORS is not authentication: every sensitive endpoint still enforces its own authorisation.
Allowlist instead of reflecting the Origin
Vulnerable
res.set("Access-Control-Allow-Origin", req.get("Origin") ?? "*");
res.set("Access-Control-Allow-Credentials", "true");Hardened
const ALLOWED = new Set(["https://app.contoso-orders.example", "https://partners.contoso-orders.example"]);
app.use((req, res, next) => {
const origin = req.get("Origin");
res.vary("Origin");
if (origin && ALLOWED.has(origin)) {
res.set("Access-Control-Allow-Origin", origin);
res.set("Access-Control-Allow-Credentials", "true");
res.set("Access-Control-Allow-Methods", "GET, POST");
res.set("Access-Control-Allow-Headers", "Content-Type");
}
next();
});Use your framework's CORS package with an explicit origin list where one exists; the sketch shows the shape of the check.
Response for an approved origin
Hardened
Access-Control-Allow-Origin: https://app.contoso-orders.example
Access-Control-Allow-Credentials: true
Vary: Origin- Maintain an exact-origin allowlist in configuration and review it like any other trust list.
- Never reflect the Origin header unchecked and never trust the literal origin null.
- Do not send Access-Control-Allow-Credentials: true with a wildcard or a reflected origin.
- Match scheme, host and port exactly; avoid substring, prefix and suffix matching.
- Limit allowed methods and headers to what the application uses and set Vary: Origin.
- Do not rely on CORS for access control; keep authentication and authorisation on every sensitive endpoint.
If it already happened
Deploy the allowlist or temporarily strip the credentials header from the affected routes, and clear any shared cache entries that stored reflected-origin responses.
Review gateway logs for credentialed requests from unapproved origins, identify which users and data were returned, and rotate any tokens exposed in those responses.
Notify affected users where required, confirm the fix by sending requests with an unapproved origin and checking that no allow-origin header returns, and monitor for new mismatches.
Move CORS configuration into one reviewed shared module, add a test for unapproved origins, and add the log detection above to routine monitoring.
Check yourself
1. Which server behaviour makes a CORS policy dangerous for authenticated APIs?
2. What is the correct fix for a server that reflects arbitrary origins?
3. Why is CORS not a substitute for access control on an API?