All guides · Security

CORS Headers: Who May Read Your Responses

Browsers apply the same-origin policy: a script on attacker.example can send a request to your site, but it cannot read the response. Cross-Origin Resource Sharing (CORS) is the way to relax that rule on purpose. When your response carries Access-Control-Allow-Origin, the browser lets the named origin read it.

That is useful for public APIs, fonts and files meant to be used by other sites. On pages and APIs that show a logged-in user’s data, a loose CORS policy hands that data to any site the user visits.

The headers that matter

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
  • Access-Control-Allow-Origin names one origin that may read the response, or * for any origin.
  • Access-Control-Allow-Credentials: true lets the browser include cookies and other credentials in the cross-origin request and expose the response. This is what turns a CORS mistake into an account-data leak.
  • Vary: Origin is needed whenever the allowed origin depends on the request, so caches do not serve one origin’s answer to another.

The risky configurations

* without credentials. Any site can read the response, but the browser sends it without cookies, so it only sees what an anonymous visitor would. Fine for truly public content; worth knowing about on a normal page, where it is rarely needed.

* with credentials. Browsers refuse this combination, so it does not leak data directly. It does show that someone tried to allow credentialed access from everywhere, and the usual “fix” when it fails is worse: reflecting the request’s Origin header back.

Reflecting the Origin. A server that copies whatever Origin it receives into Access-Control-Allow-Origin, with credentials allowed, lets every website read the user’s data. This is the most common serious CORS bug.

null. Requests from sandboxed iframes, local files and some redirects carry Origin: null. An attacker can produce such a request easily, so allowing null, especially with credentials, opens the same hole as a wildcard.

Loose matching. Checking that the origin “ends with example.com” also allows evil-example.com; checking that it “contains example.com” allows example.com.attacker.net.

How to configure it safely

  1. Do not send CORS headers on pages that do not need them. A normal website rarely does. Remove headers added globally by a plugin or a copied snippet.
  2. On APIs, allow an explicit list of origins and compare exactly:
map $http_origin $cors_origin {
  default "";
  "https://app.example.com" $http_origin;
  "https://admin.example.com" $http_origin;
}

server {
  location /api/ {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Vary Origin always;
  }
}

nginx does not send a header whose value is empty, so unknown origins get no CORS header at all.

  1. Apache:
SetEnvIf Origin "^(https://(app|admin)\.example\.com)$" CORS_ORIGIN=$1
Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header always set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
Header always merge Vary Origin
  1. Use * only for public, cookie-free resources such as fonts, public JSON or a CDN bucket.
  2. Never allow null.

CORS is not a defence against requests being sent: a forged form post still reaches your server. Protect state-changing endpoints with SameSite cookies and CSRF tokens as well.

How PerfoMetrix checks this

We read the CORS headers on the page response we already downloaded. We send no extra requests to your site.

Result When
Pass no Access-Control-Allow-Origin, or a specific origin
Needs attention * or null without credentials
Fail * or null together with Access-Control-Allow-Credentials: true

A passive check cannot test origin reflection: that requires sending requests with a forged Origin header, which is outside our passive scope. Test your APIs for it yourself, for example with curl -H "Origin: https://evil.example" -I https://example.com/api/me.

Further reading: MDN: CORS and OWASP Web Security Testing Guide: testing CORS.

Last updated October 28, 2026