← All checks
SecurityTypical severity: High

Permissive CORS Scanner

Checks whether your CORS policy reflects any origin with credentials, exposing your users' data.

What it checks

This scanner checks your CORS policy to see if it accepts requests from any origin while also allowing credentials to be sent. That combination is the most dangerous possible CORS setup. It only looks at how you respond to cross-domain requests, without accessing any data.

Why it matters

If you reflect any origin with credentials, a malicious site can make requests to your API using a logged-in user's session and read the response. That exposes your users' private data to third parties. It usually happens from copying an open configuration found in examples or tutorials.

How to fix it

Replace the wildcard origin with an explicit allowlist of trusted domains that actually need access to your API. If you do not need to send credentials across domains, turn that option off. Review your backend or framework CORS configuration and avoid automatically reflecting whatever origin arrives.

How this check works

Scanaris requests your home page again with an Origin header from a made-up domain, as the browser would if another website tried to call you, and looks at what your server answers. If Access-Control-Allow-Origin returns that same origin and Access-Control-Allow-Credentials is true, it opens a high-severity finding: your server reflects any origin with credentials. If it answers with the * wildcard together with credentials, it opens a low-severity one: browsers reject that combination, but it reveals a configuration made blindly. Only the scanned URL is tested; if your API lives on another path or domain, test it with the command below.

What an attacker does with this

They set up a page on any domain with a fetch to your API using credentials: 'include'. When one of your users, with an open session, visits that page, the browser attaches their cookies, your server answers with the user's data and, since the CORS policy allows it, the attacker's page reads it. Profile, orders, messages, whatever the API returns. The user sees nothing: it happens in the background.

Why it happens

CORS exists so the browser blocks cross-domain reads by default. When a call fails in development (the app on one port, the API on another), the quick fix suggested by tutorials and AI tools is "allow all origins", and to make cookies work "with credentials" gets added. In production that means the protection was switched off. More detail in misconfigured CORS: the silent flaw that exposes your users.

How to fix it

  • Explicit list of origins. Only the domains that genuinely call your API from the browser. In Express: cors({ origin: ['https://app.yourdomain.com'], credentials: true }). In Next.js, in the Route Handler, compare the Origin against your list before returning it. In Nuxt, with routeRules and the cors option or a server middleware.
  • Never reflect the origin. Copying the value of Origin into the response "because then everything works" is the exact mistake the check measures.
  • If you do not need cookies across domains, remove Access-Control-Allow-Credentials. Without credentials, a * on a public read-only API is acceptable.
  • Add Vary: Origin when the response depends on the origin, so that no cache serves one domain the response meant for another.
  • Cookies with SameSite=Lax or Strict as a second barrier: even if CORS failed, the browser would not send them from another site.

Check it is fixed

curl -sI -H "Origin: https://evil.example" https://yourdomain.com/api/me | grep -i "access-control"

The right outcome is that Access-Control-Allow-Origin does not appear for that origin, or appears with your domain and not with evil.example. Also test with your own domain in Origin to confirm your app still works.

Frequently asked questions

My API is public with no login. Is it a problem?

No, if there are no cookies or authorization headers. Access-Control-Allow-Origin: * without credentials is the correct configuration for an open API.

I have several subdomains calling the API.

List them all or validate the Origin against a strict pattern of your root domain. Never validate with "contains yourdomain.com": yourdomain.com.evil.example passes that test.

What about localhost for development?

Add it only in the development environment, through an environment variable. An http://localhost:3000 in the production list is an origin that any program on the user's computer can use.

Check it on your site

Scan your site free and see if this and 44 other checks pass, with a fix prompt for every issue.

Scan my site free

From the blog

Related checks