Safeguard
Open Source

The cors npm Package: A Security Review and Safe Usage Guide

The cors npm package is the standard CORS middleware for Express, and most of its danger comes from misconfiguration, not the library itself. Here is how to set it correctly.

Marcus Chen
DevSecOps Engineer
5 min read

The cors npm package is safe and well-maintained; nearly every CORS security incident traces back to how developers configure it, not to a flaw in the middleware. With over 60 million weekly downloads, cors is the default way Express apps handle cross-origin requests. The library does its job correctly. The problem is that its most permissive settings are the easiest to reach, and copy-pasted examples routinely ship an origin policy that trusts the entire internet.

What CORS is protecting

The same-origin policy stops a page on evil.com from reading responses to requests it makes to yourbank.com. Cross-Origin Resource Sharing (CORS) is the controlled exception: it lets your API tell the browser which other origins are allowed to read its responses. The cors middleware sets the Access-Control-Allow-Origin and related headers that carry that decision.

The critical mental model: CORS is enforced by the browser, and it governs whether JavaScript on another origin can read your response. It is not a firewall. A permissive CORS policy does not let attackers do anything a direct HTTP client could not already do; it lets a victim's browser, carrying the victim's cookies, do it on the attacker's behalf. That is why the credentials case is where things get dangerous.

The wildcard-with-credentials trap

The most common serious misconfiguration looks harmless:

const cors = require('cors');
app.use(cors({ origin: '*', credentials: true }));

Browsers actually forbid this exact combination for a reason, so what teams do next is worse: they reflect the request's Origin header back to satisfy the browser while still sending credentials.

// Dangerous: reflects ANY origin and allows credentials
app.use(cors({
  origin: (origin, callback) => callback(null, true),
  credentials: true,
}));

This tells the browser that every origin is trusted and that cookies should be included. Now any malicious site a logged-in user visits can make authenticated requests to your API and read the responses. It is a textbook cross-site data-theft setup, and cors will do exactly what you told it to.

Configure an explicit allowlist

The safe pattern is a fixed list of origins you actually trust, checked on each request:

const allowedOrigins = new Set([
  'https://app.example.com',
  'https://admin.example.com',
]);

app.use(cors({
  origin: (origin, callback) => {
    // allow same-origin / non-browser requests with no Origin header
    if (!origin || allowedOrigins.has(origin)) {
      return callback(null, true);
    }
    return callback(new Error('Origin not allowed by CORS'));
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

A few things make this correct rather than merely functional:

  • The allowlist is an exact-match set. No regex like /example\.com$/ that also matches example.com.evil.com.
  • Credentials are only enabled because there is a real allowlist behind them.
  • Methods and headers are scoped to what the API uses, rather than left wide open.

If you serve a public, credential-free API (no cookies, no Authorization sent by the browser automatically), origin: '*' with credentials: false is genuinely fine. The wildcard is only a problem when combined with credentials.

Subdomain and preview-environment edges

Real apps have dynamic origins: PR preview deployments on *.vercel.app, per-tenant subdomains, staging hosts. The temptation is to loosen the check to a broad pattern. Resist it. Prefer an explicit list built from configuration or an environment variable, and if you must match a pattern, anchor it tightly and test it against hostile inputs:

const isAllowed = (origin) =>
  /^https:\/\/[a-z0-9-]+\.preview\.example\.com$/.test(origin);

Note the anchors, the restricted character class, and the fixed suffix. A sloppy regex here is functionally identical to reflecting any origin.

Is the package itself trustworthy?

Yes. The cors package is maintained under the Express organization, follows a coordinated security-disclosure process through GitHub Security Advisories, and has a clean track record as a small, focused piece of middleware. It has not been the source of the kind of high-profile compromise that hits sprawling dependencies. Still, treat it like any supply-chain dependency:

  • Pin the version and keep your lockfile committed.
  • Let a dependency scanner watch for advisories, including in the transitive graph. An SCA tool flags a vulnerable pin even when cors arrived through another package.
  • Review the diff on major upgrades, though for a library this stable those are rare.

The honest summary: the cors npm package is not where your risk lives. Your risk lives in the four lines of config you write around it. Get the origin allowlist right, be deliberate about credentials, and this dependency is one of the safer ones in your package.json.

FAQ

Is the cors npm package safe to use?

Yes. It is the standard, well-maintained CORS middleware for Express with tens of millions of weekly downloads and a coordinated security process. The security risk comes from misconfiguration, chiefly overly permissive origin settings, not from the library.

Why can't I use origin '*' with credentials true?

Browsers explicitly reject the wildcard origin when credentials are enabled, because it would let any site make authenticated cross-origin requests. The insecure workaround of reflecting every origin back recreates exactly the risk the browser was preventing. Use an explicit allowlist instead.

How do I allow multiple origins?

Pass a function to the origin option that checks the incoming origin against an exact-match allowlist (a Set or array) and calls back with true only for trusted origins. Avoid loose regular expressions that can match attacker-controlled subdomains.

Does CORS protect my API from attackers?

No. CORS is a browser-enforced control over whether other origins' JavaScript can read your responses. It is not authentication or a firewall. You still need proper auth, input validation, and rate limiting; CORS only governs cross-origin read access from browsers.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.