Access-Control-Allow-Origin: * is one of the most common CORS misconfigurations found in production APIs. It appears in security scan reports as CWE-942 (Permissive Cross-domain Policy with Untrusted Domains), and it comes up regularly in penetration testing findings.
This guide explains what the wildcard does, why it’s a problem, and how to configure CORS correctly for your use case.
What is CORS?
Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which origins can make requests to your server. By default, browsers block cross-origin requests from JavaScript (the Same-Origin Policy). CORS headers let servers selectively relax that restriction.
When your frontend at https://app.example.com makes an API call to https://api.example.com, the browser checks:
- Does the API response include
Access-Control-Allow-Origin: https://app.example.com? - If yes, allow the response to be read by JavaScript.
- If no, block it.
The wildcard * tells browsers: any origin is allowed to read this response.
What does Access-Control-Allow-Origin: * actually allow?
The wildcard permits any website on the internet to:
- Make GET and POST requests to your API from JavaScript
- Read the response body — this is the critical part
Without the wildcard, an attacker’s page at https://evil.com could make a request to your API, but the browser would block the response before the attacker’s JavaScript could read it.
With *, the attacker’s JavaScript can read your API responses directly.
When is the wildcard actually dangerous?
Scenario 1: API returns sensitive data
// Attacker's page at evil.com
fetch('https://api.yourapp.com/user/profile', {
credentials: 'include' // sends cookies
})
.then(r => r.json())
.then(data => {
// If CORS wildcard is set, attacker can read this
exfiltrate(data.email, data.address, data.payment_info);
});
If your API returns user data based on session cookies and allows *, an attacker can trick a logged-in user into visiting evil.com and silently exfiltrate their data.
Important caveat: Access-Control-Allow-Origin: * cannot be combined with Access-Control-Allow-Credentials: true. If you try to use both, browsers ignore the wildcard and block the request. This limits the wildcard’s risk for cookie-authenticated endpoints — but not for token-based auth sent in headers.
Scenario 2: Bearer token in Authorization header
// Attacker's page
fetch('https://api.yourapp.com/admin/users', {
headers: {
'Authorization': 'Bearer ' + stolenToken
}
})
.then(r => r.json())
.then(data => exfiltrate(data));
If the attacker has obtained a JWT or API key (via XSS, phishing, etc.) and your API allows *, they can now read responses from any origin — including through malicious browser extensions, compromised browser plugins, or injected scripts.
Scenario 3: Internal APIs exposed to the internet
Many developers set Access-Control-Allow-Origin: * on internal APIs expecting them to only be accessed from internal networks. When those APIs are inadvertently exposed to the internet, the wildcard becomes a real attack surface.
What the wildcard does NOT allow
It’s important to be precise about what * doesn’t do:
- It does not allow cross-origin requests with cookies (
credentials: 'include') — that requires explicit origin whitelisting - It does not bypass authentication — requests still need valid credentials
- It does not allow non-simple requests (PUT, DELETE, custom headers) without a preflight response that also uses
*
The wildcard is specifically risky for APIs that return sensitive data and are accessed with bearer tokens or without authentication.
How to fix CORS wildcard
Option 1: Explicit origin whitelist (recommended)
Instead of *, specify exactly which origins are allowed:
// Node.js / Express example
const allowedOrigins = [
'https://app.yourcompany.com',
'https://admin.yourcompany.com',
];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin'); // important for caching
}
next();
});
# Django example (using django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://app.yourcompany.com",
"https://admin.yourcompany.com",
]
# NOT: CORS_ALLOW_ALL_ORIGINS = True
// Spring Boot example
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(
"https://app.yourcompany.com",
"https://admin.yourcompany.com"
)
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
};
}
}
Option 2: Dynamic origin validation
For more flexibility (e.g., allowing all subdomains of your domain):
function isAllowedOrigin(origin) {
if (!origin) return false;
try {
const url = new URL(origin);
// Allow any subdomain of yourcompany.com over HTTPS
return url.protocol === 'https:' &&
(url.hostname === 'yourcompany.com' ||
url.hostname.endsWith('.yourcompany.com'));
} catch {
return false;
}
}
app.use((req, res, next) => {
const origin = req.headers.origin;
if (isAllowedOrigin(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
next();
});
Always set Vary: Origin when dynamically reflecting origins — this prevents CDNs and proxies from caching a response with one origin’s header and serving it to a different origin.
Option 3: Wildcard is acceptable for truly public APIs
If your API intentionally serves public data (weather APIs, public datasets, open documentation endpoints), * is fine. The risk is only present when the API returns user-specific or sensitive data.
Examples where * is appropriate:
- Public read-only REST APIs (e.g.,
GET /api/v1/blog/posts) - Font servers, CDN endpoints
- Public analytics ingestion endpoints (receiving data, not returning sensitive data)
How scanners detect CORS wildcard
SAST tools detect * wildcards in CORS configuration code:
# Python — SAST flags this
response.headers['Access-Control-Allow-Origin'] = '*'
# SAST flags this too
app.config['CORS_ORIGINS'] = '*'
DAST tools detect it by making a cross-origin test request and inspecting the response headers:
curl -H "Origin: https://evil.com" -I https://api.example.com/endpoint
# If response contains: Access-Control-Allow-Origin: *
# → CORS wildcard misconfiguration found
Offensive360’s DAST scanner checks CORS configuration on every endpoint during a scan and flags wildcard usage when sensitive data is detected in responses.
CORS wildcard vs other CORS misconfigurations
The wildcard is actually one of the simpler CORS issues. More dangerous patterns include:
| Misconfiguration | Risk | Example |
|---|---|---|
Allow-Origin: * | Medium | Any site reads public data |
Reflecting arbitrary Origin | High | if origin: set allow-origin = origin — no validation |
Trusting null origin | High | Sandboxed iframes bypass origin checks |
| Subdomain wildcard regex flaw | High | evil.com matches .*evil.com → notevil.com passes |
Trusting http:// on HTTPS site | Medium | Downgrade attack surface |
The most dangerous pattern is reflecting the Origin header without validation — it effectively gives every website the same access as *, but also works with Access-Control-Allow-Credentials: true.
Offensive360 DAST scans automatically test CORS configuration on every endpoint, flagging wildcard usage, origin reflection without validation, and credential exposure. Run a scan on your web application for $500.