CORS: echo one trusted origin, never whatever asks
Check every Origin against a server-side allowlist so only sites you trust can read your API responses
The threat
The scanner sent a request carrying a made-up Origin header (something like https://evil.example.com) and your server echoed that exact value straight back in Access-Control-Allow-Origin. That is the signal: your API reflects whatever origin asks, instead of checking it against a short list of sites you actually trust.
Here is what that means in plain terms. Access-Control-Allow-Origin is the browser’s rule for who is allowed to read your API’s responses. When you reflect any origin, you have effectively told every website on the internet “yes, you may read this.” If credentials are in play too, any site a logged-in visitor happens to open can quietly read their data from your API on their behalf, as if it were them.
The exact fix
The rule is simple: keep a list of origins you trust on the server, and only echo back the request’s Origin when it is on that list. Never reflect an arbitrary origin, and default to same-origin (no CORS header at all) for everything else.
Express (with the cors package). Pass a function, not origin: true (which reflects everything):
const cors = require('cors');
const allowlist = new Set([
'https://yourdomain.com',
'https://app.yourdomain.com',
]);
app.use(cors({
origin(origin, cb) {
// same-origin and curl (no Origin header) pass through
if (!origin || allowlist.has(origin)) return cb(null, true);
return cb(null, false); // not listed: no CORS headers, browser blocks the read
},
credentials: true,
}));
Plain Node, or any framework, the same logic by hand:
const allowlist = new Set(['https://yourdomain.com']);
const origin = req.headers.origin;
if (origin && allowlist.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin'); // caches must key on Origin
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
nginx, map the trusted origins and only emit the header on a match:
# in the http { } block
map $http_origin $cors_ok {
default "";
"https://yourdomain.com" $http_origin;
"https://app.yourdomain.com" $http_origin;
}
location /api/ {
add_header Access-Control-Allow-Origin $cors_ok always;
add_header Vary Origin always;
}
Gotcha: always send
Vary: Originwhen the allowed origin depends on the request. Without it a shared cache (a CDN, the browser) can hand one origin’s allowed response to a different origin. And never build the allowlist from user input or an untrusted lookup, keep it hard-coded in config.
Verify it
curl -sI -H "Origin: https://evil.example.com" https://yourdomain.com/api/ | grep -i access-control-allow-origin
A safe result is no Access-Control-Allow-Origin line at all, meaning the stranger origin was refused. Repeat with a real allowlisted origin and you should see exactly that origin echoed back, never * and never the evil one.
Proof
This is one the ApeCyber scanner catches passively on every scan, sending a single throwaway Origin from the outside and watching whether your server reflects it back, touching nothing else. Pin cross-origin reads to an allowlist and that reflection is gone, a clean −10 off your posture score, the same kind of hardening that took dev3lop.com from a failing grade to A / 100.