CORS Misconfiguration in 2026: The Security Header That Breaks Everything When You Get It Wrong
CORS โ Cross-Origin Resource Sharing โ is the browser mechanism that decides which websites can talk to your API. Get it right and your API works securely across domains. Get it wrong and any website on the internet can read your users' data.
CORS misconfigurations are consistently in the top 10 findings on bug bounty platforms. They're easy to introduce (one wrong header value), hard to detect without testing (the application works fine from the user's perspective), and the impact ranges from information disclosure to full account takeover.
This article covers what CORS is, the five most dangerous misconfigurations, how to test for them, and how to fix them.
1. How CORS Works (30-Second Version)
When JavaScript on app.example.com makes a request to api.example.com, the browser adds an Origin header. The API responds with Access-Control-Allow-Origin (ACAO) to tell the browser whether the requesting origin is allowed to read the response.
If the ACAO header matches the requesting origin (or is *), the browser allows JavaScript to read the response. If it doesn't match, the browser blocks it. The request still reaches the server โ CORS is enforced by the browser, not the server.
For requests that include credentials (cookies, auth headers), the server must also return Access-Control-Allow-Credentials: true (ACAC). The browser will not send cookies cross-origin unless ACAC is true.
2. The Five CORS Misconfigurations That Matter
Misconfiguration 1: Wildcard Origin with Credentials (CRITICAL)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
This is the worst CORS misconfiguration. It tells the browser: "any website can make authenticated requests to this API and read the responses." An attacker's page can steal session data, read private user information, and perform actions as the authenticated user.
Note: modern browsers actually block this specific combination โ the spec says ACAO cannot be * when ACAC is true. But some older browsers and non-browser HTTP clients don't enforce this, and the intent behind the configuration is clearly wrong.
Misconfiguration 2: Origin Reflection (HIGH)
# Request
Origin: https://attacker.com
# Response
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
The server reflects whatever origin the client sends. This is functionally equivalent to a wildcard โ any website is allowed. Developers often implement this as a shortcut ("just reflect the origin back") without realising it defeats the entire purpose of CORS.
Misconfiguration 3: Null Origin Acceptance (HIGH)
# Request
Origin: null
# Response
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
The null origin is sent by browsers in several contexts: sandboxed iframes, local file:// pages, and certain redirect chains. If the server accepts null as a valid origin, an attacker can use a sandboxed iframe to make credentialed requests from a null origin.
Misconfiguration 4: Regex Bypass (MEDIUM)
# Server validates: origin ends with "example.com"
# Attacker sends: Origin: https://example.com.attacker.com
# Server accepts it โ (suffix match, not exact match)
Many CORS implementations use regex or string matching to validate origins. Common mistakes: matching the suffix instead of the full domain, forgetting to anchor the regex, or not escaping dots (so example.com matches exampleXcom).
Misconfiguration 5: Wildcard on Authenticated Endpoints (MEDIUM)
Access-Control-Allow-Origin: *
A wildcard without credentials is safe for public APIs. But if the endpoint returns user-specific data based on an API key in the URL or a custom header (not cookies), the wildcard allows any website to read that data. The browser won't send cookies, but other authentication mechanisms aren't protected by CORS.
3. How to Test for CORS Issues
CORS testing is straightforward with curl:
# Test 1: Does the server reflect arbitrary origins?
curl -s -I -H "Origin: https://attacker.com" https://your-api.com/endpoint \
| grep -i "access-control"
# Test 2: Does it accept null origin?
curl -s -I -H "Origin: null" https://your-api.com/endpoint \
| grep -i "access-control"
# Test 3: Suffix confusion
curl -s -I -H "Origin: https://your-api.com.attacker.com" https://your-api.com/endpoint \
| grep -i "access-control"
# Test 4: Preflight check
curl -s -I -X OPTIONS \
-H "Origin: https://attacker.com" \
-H "Access-Control-Request-Method: POST" \
https://your-api.com/endpoint \
| grep -i "access-control"
What to look for in responses:
Access-Control-Allow-Origin: *โ wildcard (check if endpoint is authenticated)Access-Control-Allow-Origin: https://attacker.comโ origin reflection (always bad)Access-Control-Allow-Origin: nullโ null acceptance (always bad)Access-Control-Allow-Credentials: trueโ combined with any of the above = exploitable
4. CORS on Cloud API Gateways
Cloud API gateways (AWS API Gateway, CloudFront, Azure API Management) add their own CORS handling, which can conflict with application-level CORS headers.
AWS API Gateway
API Gateway can be configured to add CORS headers at the gateway level. The most common mistake: enabling CORS with * origin at the gateway while the application also sets CORS headers. This can result in duplicate headers or the gateway's permissive configuration overriding the application's restrictive one.
CloudFront
CloudFront can cache CORS responses. If the first request comes from an allowed origin, the cached response (with that origin in ACAO) is served to all subsequent requests โ including requests from disallowed origins. Fix: include the Origin header in the CloudFront cache key.
nginx Reverse Proxy
nginx configurations often add CORS headers with add_header directives. The gotcha: add_header in a location block overrides all add_header directives from parent blocks. If you add CORS headers in a location block, you lose all other security headers set at the server level.
5. How to Fix CORS Properly
Rule 1: Use an Explicit Allowlist
ALLOWED_ORIGINS = {
'https://app.example.com',
'https://admin.example.com',
}
def cors_headers(request):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Credentials': 'true',
'Vary': 'Origin',
}
return {} # No CORS headers = browser blocks the request
Rule 2: Always Set Vary: Origin
If your CORS response varies by origin (which it should), set Vary: Origin. This tells caches (CDNs, proxies) that the response depends on the Origin header and shouldn't be served to requests with different origins.
Rule 3: Never Reflect the Origin
Don't do response.headers['ACAO'] = request.headers['Origin']. Always validate against an allowlist first.
Rule 4: Never Accept null
Remove null from your allowed origins list. There is no legitimate reason for a production application to accept the null origin.
Rule 5: Use * Only for Truly Public APIs
Wildcard is fine for public, unauthenticated APIs (CDN resources, public data feeds). If the endpoint returns any user-specific data or accepts any form of authentication, don't use wildcard.
6. CORS Security Checklist
| # | Check | Expected Result |
|---|---|---|
| 1 | Send Origin: https://attacker.com | ACAO does not reflect attacker origin |
| 2 | Send Origin: null | ACAO does not return null |
| 3 | Check for ACAO: * on authenticated endpoints | No wildcard on endpoints that return user data |
| 4 | Check for ACAC: true with wildcard or reflected origin | ACAC only set with explicit allowlisted origins |
| 5 | Send suffix-confused origin | Origin validation uses exact match, not suffix |
| 6 | Check Vary: Origin header | Present when ACAO varies by request origin |
| 7 | Test OPTIONS preflight | Preflight returns same CORS policy as actual request |
| 8 | Check CDN/proxy caching | Origin included in cache key |
Bottom Line
CORS is a security mechanism that developers often treat as a development obstacle โ something to "fix" by making it as permissive as possible. The result is APIs that technically work across domains but expose user data to any website that asks.
The fix is simple: explicit origin allowlists, no reflection, no null, no wildcard on authenticated endpoints. Eight checks, five rules, and your CORS configuration goes from "works but insecure" to "works and secure."
For the full API security testing guide: API Security Testing in 2026: The 10 Checks Every API Needs.