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-Originnames one origin that may read the response, or*for any origin.Access-Control-Allow-Credentials: truelets 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: Originis 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
- 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.
- 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.
- 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
- Use
*only for public, cookie-free resources such as fonts, public JSON or a CDN bucket. - 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.