RSTR-CORS-002 — manual Access-Control-Allow-Origin: * with credentials
Summary
Code sets the Access-Control-Allow-Origin response header to * on
a route that also sends Access-Control-Allow-Credentials: true. The
CORS spec forbids that combination — browsers reject the response and
the request fails — and if the application later "fixes" it by
reflecting the request Origin header without an allow-list, every
third-party origin gets credentialed cross-origin access to the API.
This is the same vulnerability class as RSTR-CORS-001, just expressed
via the lower-level res.setHeader / res.header API instead of the
cors middleware.
Severity
High.
Languages
JavaScript, TypeScript.
What rastray flags
Manual header writes pairing wildcard ACAO with credentials:
res.setHeader('Access-Control-Allow-Origin', '*'); // ← flagged
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', '*'); // ← flagged
res.header('Access-Control-Allow-Credentials', true);
What rastray deliberately does not flag
Access-Control-Allow-Origin: *without credentials (the public-API pattern; the spec permits it).- Headers set against a fixed allow-listed origin string.
How to fix it
Allow-list specific origins and only echo when the request Origin
matches an entry. The minimum safe pattern is:
const ALLOWED = new Set([
'https://app.example.com',
'https://admin.example.com',
]);
const origin = req.headers.origin;
if (origin && ALLOWED.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
}
Vary: Origin is essential — without it caches will serve the
allow-listed origin's response to other requesters.
How to suppress
For public, credential-less APIs the wildcard is correct; just remove
the Allow-Credentials line. If your tooling has a reason to keep
both headers, suppress per-line:
// rastray-ignore: RSTR-CORS-002 — public docs endpoint, no credentials sent