Scanverra

How to Fix CORS Misconfiguration

Cross-Origin Resource Sharing (CORS) is the mechanism that lets a browser decide whether JavaScript running on one origin can read a response from another. Get it too loose and you've handed any website on the internet a way to read data meant only for your own app.

The Misconfiguration Scanverra Flags Most

The single most dangerous CORS mistake is sending both of these headers on the same response:

The combination browsers explicitly forbid - but plenty of servers still send ittypescript
1Access-Control-Allow-Origin: *
2Access-Control-Allow-Credentials: true

Modern browsers are supposed to refuse to expose the response when both are present, but Scanverra flags it as a critical finding regardless - some HTTP client libraries and older browser versions don't enforce that refusal consistently, and a server sending this pairing at all indicates the CORS policy wasn't actually designed, just switched on to make an error go away.

One step down in severity: Access-Control-Allow-Origin: *on its own, without credentials. If the endpoint returns anything user-specific or sensitive - even without cookies, maybe via a query-string token - a wildcard still lets any site's script read it.

How This Usually Happens

  • Copy-pasted from a Stack Overflow answer. Access-Control-Allow-Origin: * is the fastest way to make a CORS error disappear during development, and it often just stays in production.
  • A framework default nobody revisited. Some API scaffolding tools enable permissive CORS out of the box for local development convenience.
  • Reflecting the Origin header to "support everyone". Dynamically echoing back whatever Origin the browser sent, instead of checking it against an allowlist, achieves the same exposure as a wildcard while looking more deliberate in the response.

How to Fix It

1. Maintain an explicit allowlist

Validate the incoming Origin against a known list, and only echo it back if it matches:

Explicit allowlist instead of a wildcard or blind reflectiontypescript
1const ALLOWED_ORIGINS = ["https://app.example.com", "https://staging.example.com"];
2
3app.use((req, res, next) => {
4  const origin = req.headers.origin;
5  if (origin && ALLOWED_ORIGINS.includes(origin)) {
6    res.setHeader("Access-Control-Allow-Origin", origin);
7    res.setHeader("Vary", "Origin");
8  }
9  next();
10});

2. Only set Allow-Credentials when you mean it

If the endpoint doesn't need cookies or HTTP auth sent cross-origin, don't set Access-Control-Allow-Credentials at all. If it does, it must be paired with a specific origin from step 1 - never a wildcard.

3. Scope CORS per-route, not globally

A public, read-only endpoint and an authenticated account API rarely need the same policy. Most frameworks let you apply CORS middleware selectively rather than one blanket rule for the whole app.

4. Don't forget preflight (OPTIONS) requests

Non-simple requests (custom headers, methods other than GET/POST) trigger a preflight OPTIONS request first - make sure your allowlist logic runs there too, not just on the actual request.

How Scanverra Detects This

Scanverra's security scan inspects the actual Access-Control-Allow-Origin and Access-Control-Allow-Credentials response headers on your site and flags the wildcard-plus-credentials combination as critical, and a bare wildcard on a non-public endpoint as medium severity - the same class of check used across its full header analysis.

FAQ

Frequently asked questions

Is a wildcard Access-Control-Allow-Origin always a problem?

Not by itself - a wildcard on a genuinely public, unauthenticated endpoint (a public API returning non-sensitive data) is fine. It becomes critical the moment it's paired with Access-Control-Allow-Credentials: true, because browsers are supposed to reject that combination, and a server that sends both anyway signals a misconfiguration attackers actively look for.

Does CORS protect my server from receiving the request at all?

No. CORS is enforced by the browser, not the server - the request still reaches your server either way. What CORS controls is whether the browser lets the calling page's JavaScript read the response. A non-browser client (curl, a script, another server) ignores CORS headers entirely.

Why does my API work fine when I test it with curl but fail in the browser?

Because CORS is a browser-only restriction. curl doesn't send an Origin header or enforce the response, so a missing or misconfigured CORS header only shows up once real page JavaScript tries to fetch() the endpoint from a different origin.

Should I just reflect whatever Origin header the request sends?

Only if you also never set Allow-Credentials: true. Reflecting the Origin dynamically is functionally identical to a wildcard for any origin that asks - it just looks more specific. If the endpoint touches authenticated data, validate the Origin against an explicit allowlist instead of reflecting it.

Free - no sign-up required

Check your CORS policy for free

Run a security scan and see every header misconfiguration Scanverra finds on your site.

Run free audit