CORS Misconfiguration: The Security Risks of Wildcard Origins
What CORS Actually Does
Cross-Origin Resource Sharing (CORS) controls which websites can make requests to your server. Without it, browsers enforce the same-origin policy — scripts on evil.com can't read responses from yourapi.com.
CORS relaxes this restriction by allowing your server to declare which origins are trusted. The problem is that many developers misconfigure CORS, effectively disabling this protection.
The Dangerous Configurations
1. Wildcard Origin with Credentials
The most common and dangerous misconfiguration:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Browsers actually block this combination — you can't use * with credentials. But some developers work around it by reflecting the request's Origin header:
// DANGEROUS — do not do this
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
This means any website can make authenticated requests to your API and read the response. An attacker's site could steal user data, modify account settings, or perform actions as the logged-in user.
2. Reflecting Origin Without Validation
Sometimes the Origin header is reflected directly without checking against an allowlist:
// DANGEROUS
const origin = req.headers['origin'];
res.setHeader('Access-Control-Allow-Origin', origin);
Always validate the origin against a known list of trusted domains.
3. Null Origin
Access-Control-Allow-Origin: null
The null origin can be triggered from sandboxed iframes and local files. Allowing it opens your API to attacks from those contexts.
4. Overly Broad Wildcards
// Intended: allow all subdomains of example.com
// Bug: also matches evil-example.com
if (origin.endsWith('example.com')) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
Substring matching on origins is a common bug. Always match the full origin including the protocol and port.
Secure CORS Configuration
Explicit Allowlist
const ALLOWED_ORIGINS = new Set([
'https://myapp.com',
'https://admin.myapp.com',
'https://staging.myapp.com',
]);
const origin = req.headers['origin'];
if (ALLOWED_ORIGINS.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
Important Headers
Vary: Origin— Always include this when the ACAO header changes based on the request. Without it, CDN caches may serve the wrong origin to the wrong requester.Access-Control-Max-Age— Cache preflight responses to reduce OPTIONS requests:Access-Control-Max-Age: 7200Access-Control-Allow-Methods— Only list methods you actually supportAccess-Control-Allow-Headers— Only list headers you actually need
When Wildcard Is OK
Access-Control-Allow-Origin: * is fine for truly public, read-only resources:
- Public CDN assets (fonts, images, CSS)
- Public APIs that don't require authentication
- Open data endpoints
The key rule: never combine * with Access-Control-Allow-Credentials: true, and never reflect origins without validation when credentials are involved.
Testing Your CORS Configuration
You can test with curl:
curl -I -H "Origin: https://evil.com" https://yourapi.com/endpoint
Check whether the response includes Access-Control-Allow-Origin: https://evil.com. If it does, your API is vulnerable.
Run a WebSentry scan to automatically check your site's CORS configuration and catch dangerous misconfigurations before attackers do.
Check Your Website's Security
Run a free security scan and get your A-F grade in seconds.
Scan Your Site Free