Skip to content
Security Lab · LAB-07same-origin policy / CORS

Why did the browser block that fetch?

The network tab shows a 200. Your code sees an error. Both are correct, and the difference is the whole security model.

Level
SYSTEMS
Read time
11 min
Experiment
Available
Type
INTERACTIVE

The question

A cross-origin fetch returns a CORS error in the console, yet the server logged the request and returned data. What exactly did the browser stop?

Hypothesis

The same-origin policy restricts what a document may *read*, not what it may send. The request was made; the response was withheld from your script.

Method

Laboratory available

The instrument for this investigation runs in the laboratory, where the controls, the live model and the observation log share one workstation.

Enter the laboratory

Pick a target, then change what the server sends back and what the request asks for. The bench reports which operations the browser permits and why.

What we observed

Sending is always allowed. Reading almost never is, unless the server explicitly opts the caller in. That asymmetry is deliberate: the browser cannot know whether a request is harmful, but it can stop one site from harvesting another site's responses.

PairSame originSame site
https://a.example.com and https://a.example.comyesyes
https://a.example.com and https://b.example.comnoyes
https://example.com and http://example.comnono (scheme counts)
https://example.com and https://example.com:8443noyes
https://example.com and https://partner.testnono
Site and origin are different units

Why it happens

CORS is not a restriction added on top of the web. It is the mechanism that *relaxes* an older restriction. Before it existed, a cross-origin response was simply unreadable; CORS gives the server a way to say which origins may read what.

response.httphttp
HTTP/1.1 200 OKContent-Type: application/jsonAccess-Control-Allow-Origin: https://app.example.comAccess-Control-Allow-Credentials: trueVary: Origin
The server, not the client, grants access

A preflight appears when the request is not simple: a method other than GET, HEAD or POST, a Content-Type outside the three form-ish values, or custom headers. The browser sends OPTIONS first and refuses to send the real request until the server approves it.

The Why MachineDEPTH 1 / 4

Why can a page load an image from anywhere but not read a JSON response?

  1. Because embedding a resource and reading its bytes are different capabilities.

Further research

Cookies have their own axis. SameSite=Lax is the modern default and withholds cookies on cross-site subresource requests while still sending them on top-level navigations. SameSite=None restores the old behaviour but requires Secure, and is increasingly restricted by browsers phasing out third-party cookies.

References

  1. 01MDNSame-origin policy
  2. 02WHATWGFetch Standard, CORS protocol
  3. 03web.devSameSite cookies explained