
Cross-Origin Resource Sharing (CORS) is how a server tells a browser which other origins may read its responses. It is a browser-enforced relaxation of the same-origin policy, not an access control on the server, so it does nothing to stop requests from tools that are not browsers. The main risk is a policy that reflects any origin back while also allowing credentials, which can let another site read authenticated responses in a signed-in user's browser.
Safer and riskier CORS setups
The two headers that matter most are Access-Control-Allow-Origin and Access-Control-Allow-Credentials. This table compares common setups.
| Setup | Risk | Why |
|---|---|---|
| Access-Control-Allow-Origin: * without credentials | Generally acceptable for public data | Fine for truly public, read-only data. Browsers send these requests without credentials, so cookies and other credentials are not included. |
| Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true | Does not work | Browsers reject this combination and do not share the response. Teams who hit this error sometimes switch to reflecting the origin, which is riskier. |
| Reflecting any Origin header back, with Access-Control-Allow-Credentials: true | High | Any site can read authenticated responses in a signed-in user's browser. |
| Allowing the null origin | Risky | Sandboxed iframes and some file contexts send Origin: null, so allowing it can let untrusted content read responses. |
| Loose suffix or prefix matching | Risky | A check that accepts any origin ending in example.test also accepts https://attackerexample.test. A prefix check on https://example.test also accepts https://example.test.evil.example. |
| Explicit allowlist with exact match, plus Vary: Origin | Safer pattern | Only listed origins get the header, and Vary: Origin keeps caches from serving one origin's response to another. |
CORS is not a CSRF defense
A permissive CORS policy does not protect you from Cross-Site Request Forgery, and a strict one is not a substitute for anti-CSRF tokens. Treat them as separate controls. Anti-CSRF tokens remain your first line of defense against forged requests, and CORS controls who can read responses.
Configure it safely
Prefer an explicit allowlist of the exact origins that need access, and avoid pairing wildcards with credentials. A safer response looks like this:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: OriginSet the origin per request from a known allowlist rather than echoing it blindly, and include Vary: Origin so caches do not serve one origin's response to another.
How to check your own API
You can run a quick self-check against an API you own. Send a request with an origin you do not trust and read the response headers:
curl -s -o /dev/null -D - -H "Origin: https://evil.example" https://api.example.com/endpointThis sends a normal GET request and prints only the response headers. It avoids -I, which sends a HEAD request, because some APIs route HEAD differently from GET and can return different headers.
Look for Access-Control-Allow-Origin and Access-Control-Allow-Credentials in the response. If the untrusted origin comes back with credentials allowed, the policy is reflecting origins.
Illustrative risky response:
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: trueIllustrative safer response, with no Access-Control-Allow-Origin header for the unknown origin:
HTTP/1.1 200 OK
Content-Type: application/json
Vary: OriginRepeat the check with -H "Origin: null", and with lookalike hostnames such as https://attackerexample.test for an API on example.test, to catch null-origin and loose matching rules. Check each endpoint that returns sensitive data, since policies are sometimes set per route.
Fix checklist
- Use an explicit allowlist of exact origins, including scheme and port, and compare origins exactly.
- Return
Access-Control-Allow-Originonly for origins on the allowlist, and send no CORS headers for anything else. - Send
Access-Control-Allow-Credentials: trueonly where cross-origin credentialed requests are actually needed. - Do not allow the
nullorigin. - Use
*only for public, read-only data that needs no credentials. - Add
Vary: Originwhenever the header value changes by origin. - Re-run the self-check after each change, and keep authentication and anti-CSRF controls in place, since CORS is not an access control.
A tight CORS policy reduces risk, but it is one layer among several. A misconfigured policy is a common finding in a web application security test. If you would like us to review your API's CORS policy along with the rest of the application, we can include it in a scoped web application test against a scope we agree together. Contact us to get started.

Have a system you want tested?
Tell us what you are working with and we will follow up with next steps.