Glossary

What is CORS — how it works, and what a misconfiguration exposes

CORS controls whether another site's JavaScript can read your API responses. How a misconfiguration exposes logged-in data, and safe allowlist settings.

Published 2026-06-11 Updated 2026-10-06 7 min read
On this page

An API that works after you add Access-Control-Allow-Origin is not necessarily safe. CORS isn't something you add once and forget; what matters is narrowing who you allow. Here's how it works and how to set it safely (no attack steps).

First, how it works

Browsers enforce a same-origin policy: JS from another origin can't freely read another site's responses. CORS exists to open that exception only to parties the server explicitly allows.

An origin is the combination of scheme (https), host name and port. https://example.com and https://api.example.com have different hosts, so they are different origins; http://example.com and https://example.com differ in scheme, so they are different too.

Response headerMeaning
Access-Control-Allow-OriginWhich origins may read the response
Access-Control-Allow-CredentialsWhether requests with credentials (cookies) are allowed
Access-Control-Allow-Methods / -HeadersWhich HTTP methods/headers are allowed

The point: CORS is on the "loosen the restriction" side. Loosen it wrong and data that shouldn't be readable becomes readable to third parties.

One more thing that matters: what CORS stops is JavaScript reading the response. A simple request reaches the server and is processed even without CORS permission. For cases like custom headers, the browser first checks with an OPTIONS request (a preflight) and doesn't send the real request if it isn't allowed.

How it differs from similar mechanisms

CORS is often confused with similarly named mechanisms and with a closely related vulnerability.

TermWhat it decidesWho enforces it
Same-origin policyOther origins can't read responses (browser default)The browser
CORSAs an exception to that, which origins may readThe server declares, the browser enforces
CSRFAn attack that makes the user's logged-in browser send unintended requestsPrevented with CSRF tokens and SameSite
CSPWhich scripts and connections your own page may loadThe server declares, the browser enforces

CORS is about "can the response be read"; CSRF is about "can a request be sent on the user's behalf." Tightening CORS doesn't prevent CSRF.

What makes a setting dangerous

Dangerous settings (common)

  • Reflecting the request's Origin back as allowed
  • Access-Control-Allow-Origin: * plus allowing credentials
  • Opening APIs broadly cross-origin that don't need it

Safe settings

  • An allowlist (only fixed, trusted origins)
  • Default deny for everything else
  • For credentialed requests, a specific origin, not *
An attacker site's JS requests your API (the user is logged in)
↓ the server blindly allows the Origin (misconfiguration)
the browser permits reading the response = personal data/tokens reach the attacker site
With a loose setting, an attacker site's JS can use the user's logged-in state to read the API response.

How misconfigurations usually happen

Most misconfigurations come from trying to make a CORS error go away. The typical sequence:

  1. Calling the API from the front end with cookies produces a CORS error
  2. You add Access-Control-Allow-Origin: *. But for credentialed requests the browser won't let the page read a * response, so the error remains
  3. You switch to echoing back the incoming Origin, and the error disappears. At the same time, any site can now read logged-in responses

Even with an allowlist, a sloppy comparison leads to the same place.

  • Matching "ends with example.com" also lets in a different domain such as evil-example.com
  • Matching "starts with https://example.com" also lets in https://example.com.evil.net
  • Allowing the null origin for development convenience also allows reads from null origins that other sites can produce

Compare the whole origin (scheme, host and port) for an exact match.

The basics of a safe setting

1

Use an allowlist (most important)

Permit only pre-decided trusted origins and deny the rest (default deny). Don't "just allow everything."
2

Don't blindly reflect Origin

Don't echo the request's Origin straight into Access-Control-Allow-Origin. Return it only when it matches the allowlist. Because the value now varies per request, also send Vary: Origin so caches don't mix responses up.
3

Never use * with credentials

Don't combine Access-Control-Allow-Origin: * with Allow-Credentials: true. Name a specific origin instead.
4

Minimize what you open

Only the endpoints that genuinely need to be cross-origin. Don't open broadly. Use * only for public data that needs no login.

How to check your own site

Send your API a request that claims a different origin and look at the response headers. Do this only against APIs you manage.

# Claim an origin you haven't allowed, and look only at the response headers
curl -s -o /dev/null -D - -H "Origin: https://example.com" https://your-api.example/ | grep -i "^access-control"
  • If you get Access-Control-Allow-Origin: https://example.com, the API reflects the Origin. Add allowlist matching
  • If both Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true appear, revisit the setting
  • If nothing comes back, or only your own front end's origin does, you're fine

Repeat the same check with Origin: null. For a site whose ownership you've verified, the site security audit checks origin reflection and credential settings together. Other security headers can be checked with the security headers checker.

This site's view: CORS is 'how you loosen,' not 'defense' — narrow who you open to

A common misconception is "adding CORS headers makes it safe." It's the opposite: CORS loosens the browser's restriction. So what matters isn't "how you add it" but "minimizing who, and how far, you open to." On this site we separate data that's fine to read externally from data that isn't, and recommend default deny, with an allowlist opening only the exceptions. Note that XSS (which breaks the read assumption) and CSRF (which targets state changes) are separate bugs — CORS alone doesn't cover everything.

Sources (primary)

  • MDN Web Docs, "Cross-Origin Resource Sharing (CORS)" — developer.mozilla.org (no * with credentialed requests, preflight conditions, Vary: Origin)
  • WHATWG Fetch Standard, "CORS protocol" — fetch.spec.whatwg.org
  • OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org (allow only trusted domains; don't return the Origin header unchecked)
  • PortSwigger Web Security Academy, "Cross-origin resource sharing (CORS)" — portswigger.net (allowlist matching errors, the null origin, CORS is not CSRF protection)
  • MITRE CWE-942: Permissive Cross-domain Policy with Untrusted Domains — cwe.mitre.org

Classification

CWE classifies the types of weakness behind vulnerabilities; MITRE ATT&CK classifies attacker techniques. Both are maintained by MITRE and widely used to label vulnerability reports and detection rules.

CWE™: Copyright © 2006–2026, The MITRE Corporation. CWE is a trademark of The MITRE Corporation. The MITRE Corporation hereby grants you a non-exclusive, royalty-free license to use CWE for research, development, and commercial purposes. Any copy you make for such purposes is authorized on the condition that you reproduce MITRE’s copyright designation and this license in any such copy. CWE Terms of Use

FAQ

QWhat is CORS for?
A

Browsers enforce a same-origin policy: JavaScript from one origin (site) can't freely read another site's responses. CORS (Cross-Origin Resource Sharing) is how a server opens that exception — but only to parties it explicitly allows. Response headers like Access-Control-Allow-Origin declare which origins may read the response.

QWhat does a CORS misconfiguration expose?
A

Too-loose permissions can let an attacker's site read a user's logged-in API responses via JavaScript. Classic cases: reflecting the request's Origin back as allowed, or setting Access-Control-Allow-Origin to * while also allowing credentials (cookies). The result is that personal data or tokens can reach a third-party site.

QWhat's the basis of a safe setting?
A

An allowlist. Permit only pre-decided trusted origins and deny the rest (default deny). Don't blindly reflect the request's Origin. For credentialed requests, don't use * for Access-Control-Allow-Origin — name a specific origin. And only expose the endpoints that genuinely need to be cross-origin.

QI'm getting a CORS error — should I set Access-Control-Allow-Origin to *?
A

For an API that only returns public data with no login, * is fine. But for requests that carry credentials such as cookies, the browser won't let the page read a * response, so the error stays. If you then switch to echoing back whatever Origin arrives, any site can read the response. The right fix is to list your own front end's origin in an allowlist and return it only on an exact match.

QDoes configuring CORS prevent CSRF?
A

No. CORS controls whether a response can be read, not whether a request is sent. Simple requests (such as a POST shaped like a form submission) reach the server even without CORS permission. Prevent CSRF with CSRF tokens and the cookie SameSite attribute.

QWhat is a preflight request?
A

Before sending the real request, the browser asks the server with an OPTIONS request whether that method and those headers are allowed. It happens with custom headers, a POST with application/json, PUT, DELETE and similar. If it isn't allowed, the real request is not sent. Simple requests — GET, HEAD or POST using only the permitted headers and content types — don't trigger one.