← All guides
High

CORS: who is allowed to talk to your website from another site

A misconfigured sharing rule can let any website on the internet read your customers' private data. How it goes wrong and how to set it right.

7 minute read · Browser protections

The short version

Browsers have a built-in rule: a page on one website is not allowed to read data from another website. That rule is what stops a random site you visit from quietly reading your emails from Gmail in the background. CORS (Cross-Origin Resource Sharing) is the mechanism that lets a site *relax* that rule when it has a good reason. Get the setting wrong and you have told every website on the internet "yes, you may read my customers' private data". It is a high-severity finding because the misconfiguration is easy to make and the consequence is a direct data leak.

The rule it relaxes

Imagine you are logged in to your online banking in one tab and you open a dodgy website in another. The dodgy page runs a script that tries to fetch yourbank.co.uk/account/balance. Your browser will happily *send* that request — with your banking cookie attached, because that is how cookies work — but it will refuse to let the dodgy page *read the response*. That refusal is the same-origin policy, and it is one of the most important security rules in the web.

CORS is how a server opts out of that protection for specific other sites. It does so with a response header: Access-Control-Allow-Origin, naming the site that is allowed to read the response. Legitimate uses are everywhere: your mobile app's backend being read by your web app on a different domain, a public API, fonts served from a CDN.

The two ways it goes badly wrong

  • Reflecting the origin. The server reads the Origin header from the incoming request and simply copies it back into Access-Control-Allow-Origin — so *whatever* site asks is allowed. Developers do this because it "makes CORS errors go away". Combined with Access-Control-Allow-Credentials: true, it means any website can make a logged-in visitor's browser fetch their private data from your site and read it. This is the dangerous one.
  • Wildcard with credentials. Sending Access-Control-Allow-Origin: * is fine for genuinely public data (a public API, fonts). Browsers refuse to combine * with credentials, so it cannot leak logged-in data directly — but servers that send * on endpoints that return personal data are usually one small change away from the reflection problem, and it signals the setting was never thought through.

Related sloppiness we also look for: null as an allowed origin (which attackers can trigger from sandboxed frames), and allowing origins by a loose pattern that an attacker can match with a lookalike domain such as yourbusiness.co.uk.evil.com.

What a safe configuration looks like

The principle is simple: name exactly the sites that need access, and nothing else.

  • If no other site needs to read your responses — true for most business websites — do not send CORS headers at all. The default browser behaviour is the safe one.
  • If a specific site of yours needs access (say app.yourbusiness.co.uk reading from api.yourbusiness.co.uk), check the incoming Origin against a fixed allow-list and only echo it back if it matches exactly. Add Vary: Origin so caches do not serve one site's answer to another.
  • Only send Access-Control-Allow-Credentials: true if that other site really needs to send cookies, and never alongside a wildcard or a reflected origin.
  • For genuinely public, non-personal data, Access-Control-Allow-Origin: * without credentials is correct and safe.

In PHP, a safe allow-list looks like this:

$allowed = ['https://app.yourbusiness.co.uk'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowed, true)) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Access-Control-Allow-Credentials: true');
    header('Vary: Origin');
}

What VigilScan checks

We request your homepage with an Origin header set to a domain we control, and look at what comes back. If the server reflects our origin — or returns * together with credentials, or allows null — we flag it, and show you the exact headers we received so your developer can find the setting. On verified domains, deeper scans repeat this against the API-style paths we discover.

A reflected-origin finding on a site with customer logins should be treated as urgent. It means the private data of anyone logged in to your site can be read by any other site they visit.

Common questions

My developer added the header to fix a "blocked by CORS policy" error. Was that wrong?
Not necessarily — that error is the browser doing its job, and the fix is to allow the *specific* site that needs access. The mistake is allowing everyone. Ask them to replace any wildcard or reflected origin with a fixed list of your own domains.
We use a CDN for images and fonts with a wildcard. Is that a problem?
No. Fonts and images are public and carry no personal data, and a wildcard without credentials is the correct setting for them. The concern is wildcards or reflection on responses that contain anything about a logged-in person.
Does CORS protect my site from being attacked?
It is the other way round: the browser's same-origin rule protects your *visitors*, and CORS is the knob that loosens it. A good CORS setup is one that loosens it as little as possible.

Not sure if this affects your site?

Run a free check — it takes about 90 seconds and tells you exactly where you stand.

Check my website