CORS Scanner
Cross-origin rules decide which websites may read your API with a visitor's cookies. The scanner sends three hostile Origin values to one endpoint per route group and checks whether the server agrees to them.
What gets probed
CORS is a property of a route group, not of a single URL, so the scanner does not hit every endpoint. It picks one endpoint per path prefix, then sends that endpoint three different Origin values and reads what the server says back.
1Discovery produced many endpoints. The scanner keeps the first endpoint it sees for each distinct two-segment path prefix, so /api/users/1 stands in for everything under api/users.
2Each pick gets a GET with Origin: https://evil.com, then null, then https://<target-host>.evil.com. The third catches servers that match the origin by suffix or substring.
3The scanner stops at the first probe that produces a finding, so one endpoint yields at most one finding.
4No Access-Control-Allow-Origin in the response ends that probe silently. Without it the browser refuses to expose the response to another site, so there is nothing to report.
Reading the response
Two headers decide everything: Access-Control-Allow-Origin (ACAO) and Access-Control-Allow-Credentials (ACAC). The checks run in a fixed order and the first match wins.
1Wildcard plus credentials is CRITICAL, 0.95. ACAO: * together with ACAC: true is the worst case: any website can read authenticated responses.
2An echoed origin is HIGH, 0.90, when credentials are allowed. If ACAO equals the exact Origin the scanner sent, the server mirrors any caller. With ACAC: true the visitor's cookies ride along; without it only credential-less data is exposed, so it drops to MEDIUM, 0.70.
3ACAO: null is HIGH, 0.85, when credentials are allowed (MEDIUM, 0.70 otherwise). A page in a sandboxed iframe sends Origin: null, so allowing it lets an attacker reach the API from a sandbox. This rule is checked before the echoed-origin rule, so a null-only server gets this title.
4A wildcard without credentials is MEDIUM, 0.70. Any site can read the response, but not with the victim's cookies.
5Anything else is silent: a fixed allowed origin, or no header. A response with no ACAO header never reaches these rules at all.
What an allowlist changes
The fix is to echo an origin only when it is on a list you wrote. The scanner's probes then come back with no ACAO header, and the browser keeps the response from the attacker's page.
1An origin that is not on the allowlist gets no CORS headers at all. The browser then refuses to hand the response to the attacker's script.
2An origin on the list gets that single origin echoed back, with credentials allowed. Only that front end can read authenticated responses.
3This is why the scanner's three probes (evil.com, null, <host>.evil.com) all come back silent on a correct setup.
Why it matters
A CORS misconfiguration lets another website read your API as the logged-in user. If a user is signed in to your app and visits evil.com, evil.com's JavaScript can fetch your API and read account data, messages and tokens, and can also send state-changing requests from the victim's browser session.
Several exchanges were found reflecting arbitrary Origin headers in CORS responses. That allowed attacker-controlled websites to read authenticated API responses containing balances and API keys.
Vulnerable vs fixed code
// BAD: Wildcard CORS with credentials
response.headers.set("Access-Control-Allow-Origin", "*")
response.headers.set("Access-Control-Allow-Credentials", "true")
// BAD: Reflecting the Origin header without validation
const origin = request.headers.get("Origin")
response.headers.set("Access-Control-Allow-Origin", origin)// GOOD: Allowlist specific origins
const ALLOWED_ORIGINS = ["https://your-app.com", "https://admin.your-app.com"]
const origin = request.headers.get("Origin")
if (ALLOWED_ORIGINS.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
response.headers.set("Access-Control-Allow-Credentials", "true")
}
// If origin not in list, no CORS headers = browser blocks the requestLimits and what it needs
| Rule | Reports when | Severity · confidence |
|---|---|---|
| Wildcard + credentials | ACAO: * and ACAC: true | Critical · 0.95 |
| Null origin (checked first) | ACAO: null | High · 0.85 with ACAC: true, otherwise Medium · 0.70 |
| Origin reflected | ACAO equals the Origin sent (not *) | High · 0.90 with ACAC: true, otherwise Medium · 0.70 |
| Wildcard, no credentials | ACAO: * without ACAC: true | Medium · 0.70 |
The probes are plain GET requests, so a route that only sets CORS headers on an OPTIONS preflight is not seen. A response with no Access-Control-Allow-Origin is never reported, and only one endpoint per path prefix is tested, so a single route that differs from its siblings can be missed. Credentials are optional: with them, the probes carry the scan's auth headers.
The null-origin rule is evaluated before the echoed-origin rule, so a server that answers ACAO: null to the Origin: null probe is reported under the null-origin title.