Insecure CORS: When origin: '*' Exposes Your API
The "fix CORS errors" prompt creates the most permissive - and dangerous - configuration
Quick Answer
CORS controls which websites can access your API. Setting Access-Control-Allow-Origin: * allows ANY website to read your API responses - including attacker sites. AI tools use this because it "fixes" CORS errors. Always whitelist specific origins instead. Classified as #5 Security Misconfiguration in OWASP Top 10.
Security Classification
What is CORS?
Cross-Origin Resource Sharing (CORS) is browser security that controls which websites can access your API. Think of it like a VIP list for your backend - only websites you approve can read responses from your API endpoints.
Browsers enforce the Same-Origin Policy by default, blocking cross-site requests. CORS headers tell browsers to make exceptions for trusted origins. The problem starts when vibe coders tell AI tools to "fix CORS errors" - the AI responds with origin: '*', which lets everyone in.
According to MDN's CORS documentation, the Access-Control-Allow-Origin header must specify either a single origin or the * wildcard. The wildcard means your API is effectively public to any website on the internet.
How Do AI Tools Generate Insecure CORS?
When vibe coding, the typical pattern is: you build a frontend, try to call your API, and get a CORS error. You ask the AI to "fix CORS" or "enable CORS", and it immediately generates origin: '*' because that makes the error disappear.
Insecure CORS defaults are commonly found in AI-generated code. The AI optimizes for "working code" without considering that wildcard CORS + authenticated APIs = anyone can steal your users' data. See our Claude Code security patterns guide for more details.
This is part of OWASP's Security Misconfiguration category - insecure defaults that "work" but create vulnerabilities.
What Are the Common CORS Misconfigurations?
PortSwigger's CORS research documents several misconfiguration patterns, all of which appear in vibe coded applications:
Wildcard Origin
HighSetting Access-Control-Allow-Origin: * allows ANY website to read your API responses. AI tools generate this because it immediately fixes CORS errors.
// VULNERABLE: AI generates this when you say "enable CORS"
// Express
import cors from 'cors'
app.use(cors()) // Defaults to origin: '*'
// Or explicitly
app.use(cors({
origin: '*', // ANY website can access your API!
}))
// Next.js API route
export async function GET(request: Request) {
return new Response(JSON.stringify({ secret: 'data' }), {
headers: {
'Access-Control-Allow-Origin': '*', // DANGEROUS
},
})
}// SECURE: Whitelist specific origins
const allowedOrigins = [
'https://myapp.com',
'https://app.myapp.com',
process.env.NODE_ENV === 'development' && 'http://localhost:3000',
].filter(Boolean)
app.use(cors({
origin: (origin, callback) => {
if (!origin) return callback(null, true)
if (allowedOrigins.includes(origin)) {
callback(null, origin)
} else {
callback(new Error('CORS not allowed'))
}
},
credentials: true,
}))Origin Reflection
CriticalReflecting the Origin header back is as dangerous as wildcard, but bypasses the browser restriction against wildcard + credentials. Attacker site sends their origin, server reflects it, attack succeeds.
// VULNERABLE: Reflecting Origin header is just as dangerous
app.use(cors({
origin: (origin, callback) => {
// This reflects ANY origin - same as wildcard!
callback(null, origin)
},
credentials: true,
}))
// Or in Next.js
export async function GET(request: Request) {
const origin = request.headers.get('origin')
return new Response(JSON.stringify({ data: 'sensitive' }), {
headers: {
'Access-Control-Allow-Origin': origin || '*', // DANGEROUS
'Access-Control-Allow-Credentials': 'true',
},
})
}// SECURE: Validate origin against whitelist before reflecting
const allowedOrigins = ['https://myapp.com', 'https://app.myapp.com']
app.use(cors({
origin: (origin, callback) => {
if (!origin) return callback(null, true)
if (allowedOrigins.includes(origin)) {
callback(null, origin) // Only reflect TRUSTED origins
} else {
callback(new Error('Not allowed'))
}
},
credentials: true,
}))Weak Regex Validation
HighUsing regex to validate origins without proper anchoring allows attackers to bypass validation. The pattern "example.com" matches "evilexample.com" and "example.com.evil.com".
// VULNERABLE: Regex without proper anchoring
const allowedPattern = /example\.com/
app.use(cors({
origin: (origin, callback) => {
if (allowedPattern.test(origin)) {
callback(null, origin) // DANGEROUS!
} else {
callback(new Error('Not allowed'))
}
},
}))
// Attacker uses: https://evilexample.com - matches!
// Attacker uses: https://example.com.evil.com - matches!// SECURE: Use exact string matching or properly anchored regex
const allowedOrigins = ['https://example.com', 'https://app.example.com']
// BEST: Exact string matching
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, origin || true)
} else {
callback(new Error('Not allowed'))
}
},
}))
// OR: Properly anchored regex
const safePattern = /^https:\/\/(app\.)?example\.com$/
// ^ = start, $ = end, \. = literal dotNull Origin Trust
MediumAccepting null as a valid origin is exploitable. Attackers can trigger null origin using sandboxed iframes. Many vibe coded APIs accept null "just in case".
// VULNERABLE: Trusting null origin
app.use(cors({
origin: (origin, callback) => {
// "null" can be triggered by attackers via sandboxed iframes
if (!origin || origin === 'null') {
callback(null, true) // DANGEROUS
} else if (allowedOrigins.includes(origin)) {
callback(null, origin)
}
},
}))
// Attacker page:
// <iframe sandbox="allow-scripts" src="https://your-api.com/data">
// This sends Origin: null// SECURE: Don't accept null origin
app.use(cors({
origin: (origin, callback) => {
// Reject null origin - only accept known origins
if (origin && allowedOrigins.includes(origin)) {
callback(null, origin)
} else if (!origin) {
// No origin = same-origin request or non-browser client
// This is safe because there's no cross-origin context
callback(null, true)
} else {
callback(new Error('Not allowed'))
}
},
}))Missing Preflight Handling
MediumForgetting to handle OPTIONS preflight requests causes CORS to fail for non-simple requests. AI tools often generate incomplete CORS that works for GET but fails for POST with JSON.
// VULNERABLE: Missing OPTIONS handler
// Next.js API route - only handles GET/POST
export async function GET(request: Request) {
return corsResponse({ data: 'works' })
}
export async function POST(request: Request) {
// POST with JSON triggers preflight
// But OPTIONS is not handled - CORS fails!
const body = await request.json()
return corsResponse({ result: 'saved' })
}
// Browser sends OPTIONS first, gets 405 Method Not Allowed// SECURE: Handle OPTIONS preflight explicitly
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': 'https://myapp.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400', // Cache preflight 24 hours
},
})
}
export async function POST(request: Request) {
const body = await request.json()
return new Response(JSON.stringify({ result: 'saved' }), {
headers: {
'Access-Control-Allow-Origin': 'https://myapp.com',
'Content-Type': 'application/json',
},
})
}How Do I Configure CORS Securely in Next.js?
Next.js offers two approaches for CORS configuration. For simple cases with a single allowed origin, use next.config.js headers:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'https://myapp.com', // Specific origin, not *
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
]
},
}For dynamic origin validation (multiple allowed origins or credentials), use middleware:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const allowedOrigins = [
'https://myapp.com',
'https://app.myapp.com',
process.env.NODE_ENV === 'development' && 'http://localhost:3000',
].filter(Boolean) as string[]
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin')
const response = NextResponse.next()
// Handle preflight
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': allowedOrigins.includes(origin || '')
? origin!
: allowedOrigins[0],
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400',
},
})
}
// Validate and set origin for actual requests
if (origin && allowedOrigins.includes(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin)
response.headers.set('Access-Control-Allow-Credentials', 'true')
}
return response
}
export const config = {
matcher: '/api/:path*',
}When Is Wildcard CORS Actually OK?
Wildcard (*) IS acceptable in specific situations. Use it only when ALL of these are true:
- API returns only public data (no user-specific information)
- No authentication or session cookies required
- Read-only operations (GET requests only)
- No sensitive business logic exposed
Examples where wildcard is fine: public status endpoints, public CDN resources, open APIs like weather data. If you're building an authenticated app with user data, wildcard is never appropriate.
How Do I Test My CORS Configuration?
According to OWASP's CORS testing guide, you should test both what's allowed and what's rejected:
# Test with malicious origin (should be rejected)
curl -i -H "Origin: https://evil.com" https://your-api.com/api/protected
# Look for: NO Access-Control-Allow-Origin header, or 403
# Test with allowed origin (should work)
curl -i -H "Origin: https://your-app.com" https://your-api.com/api/protected
# Look for: Access-Control-Allow-Origin: https://your-app.com
# Test preflight request
curl -X OPTIONS -i \
-H "Origin: https://your-app.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
https://your-api.com/api/data
# Look for: 204 status, proper CORS headers
# Test null origin (should be rejected for auth APIs)
curl -i -H "Origin: null" https://your-api.com/api/protected
# Look for: NO Access-Control-Allow-Origin headerRemember: CORS is browser-enforced. Tools like curl and Postman ignore CORS entirely, so your API "working" in Postman doesn't mean CORS is configured correctly.
AI Fix Prompt for CORS Security
Copy this prompt to your AI coding tool to audit and fix CORS misconfigurations:
Review my codebase for CORS security issues and fix them:
1. **Audit CORS Configuration**
- Search for "Access-Control-Allow-Origin" header settings
- Search for cors() middleware usage and configuration
- Find any origin: '*' or origin: true patterns
- Check for origin reflection (using request.headers.origin directly in response)
2. **Check for Wildcard Origin**
- Replace origin: '*' with explicit whitelist of allowed origins
- Never use wildcard with credentials: true (browsers block this anyway)
- Exception: Wildcard IS okay for truly public, read-only APIs with no auth
3. **Check for Origin Reflection**
- Find patterns where Origin header is echoed back without validation
- Replace with whitelist validation before reflecting origin
- This is especially dangerous with credentials: true
4. **Validate Regex Patterns**
- Check any regex used for origin validation
- Ensure regex has ^ (start) and $ (end) anchors
- Test that "example.com" doesn't match "evilexample.com"
- Prefer exact string matching over regex when possible
5. **Check Null Origin Handling**
- Don't explicitly accept "null" as a valid origin
- Null origin can be triggered by attackers via sandboxed iframes
- Treat null origin same as unknown origin (reject for authenticated APIs)
6. **Verify Preflight Handling**
- Ensure OPTIONS requests are handled for all API routes
- Return proper CORS headers in preflight response
- Set Access-Control-Max-Age to cache preflight (e.g., 86400 for 24h)
7. **Framework-Specific Checks**
- Next.js: Check next.config.js headers AND middleware.ts
- Express: Check cors() package configuration
- Check that all API routes apply CORS consistently
8. **Environment-Specific Origins**
- Use environment variables for allowed origins
- Include localhost only in development mode
- Don't hardcode production URLs in code
Here's the secure pattern to implement:
```typescript
// Secure CORS whitelist implementation
const allowedOrigins = [
process.env.FRONTEND_URL, // e.g., 'https://myapp.com'
process.env.NODE_ENV === 'development' && 'http://localhost:3000',
].filter(Boolean) as string[]
function validateOrigin(origin: string | null): string | null {
if (!origin) return null // Same-origin or non-browser
if (allowedOrigins.includes(origin)) return origin
return null // Reject unknown origins
}
```
**Test your fixes:**
```bash
# Test wildcard (should fail for authenticated APIs)
curl -H "Origin: https://evil.com" https://your-api.com/protected
# Test preflight
curl -X OPTIONS -H "Origin: https://your-app.com" \
-H "Access-Control-Request-Method: POST" \
https://your-api.com/endpoint
# Verify response headers
# Should see Access-Control-Allow-Origin: https://your-app.com
# NOT Access-Control-Allow-Origin: *
```Frequently Asked Questions
Is Access-Control-Allow-Origin: * dangerous?
Yes, for APIs that handle sensitive data or require authentication. Wildcard (*) allows ANY website to read your API responses. If your API returns user data, attackers can create a malicious page that steals it. However, wildcard IS acceptable for truly public APIs that have no authentication and return only public data.
Can I use CORS with credentials and wildcard origin?
No. Browsers explicitly block this combination - it's not allowed by the CORS specification. If you set Access-Control-Allow-Credentials: true, you cannot use wildcard (*) for Access-Control-Allow-Origin. This is why attackers use origin reflection attacks as a workaround.
How do I fix CORS errors securely?
Instead of using origin: '*', create a whitelist of allowed origins and check incoming requests against it. Only reflect the Origin header back if it matches your whitelist. For development, add localhost to the whitelist conditionally. Use environment variables for production origins.
What is CORS origin reflection vulnerability?
Origin reflection is when your server echoes back whatever Origin header the browser sends, without checking if it's trusted. This is as dangerous as wildcard because attackers can send their malicious origin, get it reflected back, and bypass CORS protection. According to PortSwigger, this is one of the most common CORS misconfigurations.
How do I configure CORS in Next.js?
Next.js offers two approaches: use next.config.js headers for static CORS settings, or use middleware.ts for dynamic origin validation. For APIs that need to support multiple origins or credentials, middleware is more flexible. Always handle OPTIONS preflight requests explicitly in your route handlers.
Scan Your Vibe Coded App for CORS Issues
VibeShip Scanner detects insecure CORS configurations, origin reflection vulnerabilities, and missing preflight handlers in your codebase.
Scan Your Code Free