Skip to content

CORS configuration

Severity: highApplies to: cors 2.xFacts last verified 2026-07-26 against cors 2.8.6

The cors package’s documented default configuration is:

{
"origin": "*",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false,
"optionsSuccessStatus": 204
}

So app.use(cors()) — the line in every getting-started guide — sends Access-Control-Allow-Origin: * to everyone. That is a deliberate default for a package used mostly on public APIs, and it is the wrong one for anything behind a login.

the fix
import cors from "cors";
const allowed = new Set(["https://app.example.com"]);
app.use(
cors({
origin(origin, cb) {
// no origin = same-origin or a non-browser client
if (!origin || allowed.has(origin)) return cb(null, origin ?? true);
cb(new Error("Not allowed by CORS"));
},
credentials: true,
})
);
verify it workedRun this in: http response
Terminal window
curl -sI -H 'Origin: https://evil.example' https://api.example.com/me \
| grep -i 'access-control-allow-'

No Access-Control-Allow-Origin in that response is the correct result. If it comes back echoing https://evil.example, the origin is being reflected.

From the package docs: setting origin to true will “reflect the request origin”.

That reads like “enable CORS” and behaves like “allow everyone” — with one crucial difference from "*". The wildcard is rejected by browsers when credentials are involved, so origin: "*" plus cookies fails safe. Reflection does not: echoing the caller’s origin back is a valid, specific value, so origin: true combined with credentials: true genuinely allows any website to make authenticated requests to your API and read the responses, using your users’ cookies.

If you take one thing from this page: origin: true with credentials: true is the combination to grep for.

Regex origins deserve the same suspicion. The docs’ own example, /example\.com$/, matches notexample.com — there is no boundary before example.

The package’s documentation includes a “Common Misconceptions” section, and it is worth quoting because it contradicts how CORS is usually described:

“CORS protects my API from unauthorized access” — No. CORS is not access control. Any HTTP client (curl, Postman, another server) can call your API regardless of CORS settings.

“CORS blocks requests from disallowed origins” — No. Your server receives and processes every request. CORS headers tell the browser whether JavaScript can read the response.

This matters operationally. A restrictive CORS policy does not stop a request from executing — the side effects still happen, and only the response is withheld from the calling page. Locking down CORS is not a substitute for authentication, and it is not a mitigation for an endpoint that should not have been public.

before you ship this

Tightening origin breaks any front end on a hostname you forgot: preview deployments, a staging domain, a mobile web wrapper, a partner embed. These fail in the browser console rather than in your logs, so nobody tells you.

Enumerate the hostnames before deploying, and prefer an explicit allow-list over a regex — the regex is where the subtle holes live.