The Same-Origin Policy
Date: 2026-08-16
A page can’t read data from a different origin. It’s the boundary the entire web security model rests on, and CORS, Content Security Policy and cookie rules are all either exceptions to it or reinforcements of it.
What it is
The same-origin policy prevents a document loaded from one origin from reading data belonging to another.
An origin is the triple: scheme + host + port. All three must match exactly.
https://shop.example.com/products ← the page
https://shop.example.com/api/basket ✓ same origin
http://shop.example.com/api ✗ different scheme
https://www.example.com/api ✗ different host
https://shop.example.com:8443/api ✗ different port
Subdomains are different origins. example.com and www.example.com are different origins. This surprises people constantly.
Why it exists
Without it, any site you visited could make a request to your bank with your cookies attached and read the response.
you visit evil.com
→ its JavaScript requests https://bank.com/balance
→ your bank cookies are attached automatically ← this happens regardless
→ evil.com reads your balance ← THIS is what SOP blocks
The crucial detail: the request is often still sent. The browser blocks reading the response, not always the sending. That asymmetry is why CSRF exists as a separate problem — a request that changes state doesn’t need its response read to do damage, which is what SameSite cookies address. See Cookies and Common Vulnerabilities.
What it blocks and doesn’t
| Action | Cross-origin allowed? |
|---|---|
fetch() / XMLHttpRequest reading a response | No, unless the server opts in via CORS |
| Reading another frame’s DOM | No |
Reading localStorage, cookies, IndexedDB | No |
Loading an image via <img> | Yes — but you can’t read its pixels from a canvas |
Loading a script via <script> | Yes — and it runs with your origin’s privileges |
| Loading a stylesheet, font, or media | Yes |
| Submitting a form | Yes |
Embedding in an <iframe> | Yes, unless the target refuses |
The <script> row is the important one. A cross-origin script isn’t sandboxed — it executes as if you wrote it, with full access to your DOM, your cookies, and your storage. That’s why every third-party tag is a trust decision rather than a performance decision. See Third-Party Scripts and Subresource Integrity.
The sanctioned exceptions
Each is a deliberate hole with its own opt-in:
- CORS — the server sends headers permitting specific origins to read its responses
postMessage— explicit, structured messaging between frames, where both sides verify the origindocument.domain— a legacy relaxation for subdomains, now deprecated and disabled by default [CHECK: current status per engine]- JSONP — the historical hack of loading data as a script. Obsolete and dangerous, since the response executes with your privileges
Where you meet it
- “Blocked by CORS policy” in the console. Almost always means the server hasn’t opted in, not that your fetch is wrong — CORS
- Canvas tainting. Drawing a cross-origin image onto a canvas makes
toDataURL()andgetImageData()throw, unless the image was loaded withcrossoriginand the server permits it - Iframe access failing on a payment or checkout frame. Correct behaviour — use
postMessage - Cookies not shared between
www.and the apex domain, or between a Shopify checkout on a different host and your storefront — Checkout Instrumentation Constraints - Analytics on subdomains.
shop.example.comandblog.example.comare separate origins for storage; a cookie must be set withDomain=example.comto be shared, and doing that exposes it to every subdomain — Identity Stitching
Related but different
- Site is broader than origin — roughly the registrable domain, so
a.example.comandb.example.comare the same site but different origins.SameSitecookies use site; the same-origin policy uses origin. Mixing the two up is the cause of a lot of confused debugging - Content Security Policy restricts what your own page may load. Same-origin policy restricts what it may read from elsewhere. They’re complementary, not alternatives