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:

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

#CheckExpected Result
1Send Origin: https://attacker.comACAO does not reflect attacker origin
2Send Origin: nullACAO does not return null
3Check for ACAO: * on authenticated endpointsNo wildcard on endpoints that return user data
4Check for ACAC: true with wildcard or reflected originACAC only set with explicit allowlisted origins
5Send suffix-confused originOrigin validation uses exact match, not suffix
6Check Vary: Origin headerPresent when ACAO varies by request origin
7Test OPTIONS preflightPreflight returns same CORS policy as actual request
8Check CDN/proxy cachingOrigin 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.

Advertisement