FoundationsGENERALBROWSER-SPECIFICSPEC-EVOLVING

Origins and the Sandbox

Scheme, host and port together form the unit of trust — and nearly every confusing browser restriction is that boundary being enforced.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

Why can my page not simply read that other page, that other API, or that file?

The user intent

A person opens a page. They expect that the page cannot read their bank balance from another tab, and they expect it without knowing that expectation exists.

The obvious build

The browser is being awkward. There is a header or a flag that turns the restriction off, and finding it is the task.

Why it breaks

The restriction is the product. A browser that let any page read any other page's data would be unusable for anything that mattered.

How it breaks in a real browser
  • The restriction is the product. A browser that let any page read any other page's data would be unusable for anything that mattered.
  • Disabling a check locally to "make it work" produces code that cannot work in production, and hides the design question until it is expensive.
  • The same error message covers several different mechanisms — same-origin policy, CORS, CSP, cookie attributes — with different fixes (CORS).
  • Some restrictions are not fixable client-side at all: if the other origin does not opt in, no amount of frontend code grants access.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • An origin is the triple of scheme, host and port. https://app.example.com and https://api.example.com are different origins; so are http:// and https:// on the same host, and so are two ports.
  • The same-origin policy is the default: code from one origin may not read the DOM, cookies, storage or responses of another (The Same-Origin Policy).
  • Cross-origin embedding is generally allowed; cross-origin reading generally is not. You may display an image from anywhere and not inspect its pixels; you may load a script and not read its source.
  • CORS is the mechanism by which a server opts in to letting a browser expose a cross-origin response to script. The request usually happens either way — CORS governs whether your code may read the answer (CORS).
  • Cookies have their own scoping rules, which are related to but not the same as origin: they are keyed by domain and path, with attributes governing cross-site behaviour (Cookies).
  • Secure contexts gate powerful APIs on transport security. Service workers, geolocation and shared memory are unavailable on an insecure origin, regardless of permissions.
  • The sandbox underneath all of this is the renderer's lack of privilege: it cannot read the filesystem or open arbitrary sockets, and asks a more privileged process for everything (The Multi-Process Browser).

What this makes the browser do

And which of it is avoidable.

  • Checking every subresource load and every script access against the origin model — cheap individually, constant in aggregate.
  • Preflighting some cross-origin requests with an OPTIONS call before the real one, which is a full extra round trip on the critical path (CORS).
  • Partitioning storage and caches by origin, and increasingly by top-level site, so that embedded content cannot use storage as a cross-site channel.

One boundary, several enforcement mechanisms

The confusion in this area comes from treating four distinct mechanisms as one thing called "CORS". They answer different questions, and a fix aimed at the wrong one does nothing.

  • A failing request with no CORS headers reports as a CORS error, because a 500 response carries no Access-Control-Allow-Origin. Check the server first (Debugging the Network).
  • A preflight is triggered by method, headers or content type — often by a header added for tracing that nobody realised was custom.
  • Credentials cross-origin need both a request that asks for them and a server that names the exact origin. Wildcard plus credentials is rejected by design.
MechanismQuestion it answersWho decidesWhat it does not do
Same-origin policyMay this code read that document, storage or response?The browser, alwaysPrevent the request from being sent
CORSMay script read this cross-origin response?The responding server, via headersAuthenticate, authorize, or protect a non-browser client
CSPMay this page load or execute this at all?The serving origin, via a headerSanitize content, or stop anything already inline and allowed
Cookie attributesIs this cookie sent on this request?The setting server, plus browser defaultsStop a cookie already sent from being honoured

Choosing an origin topology

This is a decision, usually made once, that determines how much of the above you will deal with for the life of the product. It is worth making deliberately rather than inheriting from whichever repository was created first.

Where does the API live relative to the app?

Same origin, sibling origin, or a different site entirely?

Same origin (`/api` on the app origin)

when You control both and can route at the edge. The simplest configuration that exists.

cost App and API share a deployment surface and a cache namespace; routing rules become shared infrastructure.

Sibling subdomain (`api.example.com`)

when Independent deployment matters, and cookies can be scoped to the parent domain.

cost CORS configuration, possible preflights, and cookie scoping that is easy to get subtly wrong (Cookies vs Script-Readable Tokens).

Different site entirely

when A genuinely third-party API, or a vendor you do not control.

cost Full CORS, no shared cookies, storage partitioning, and a dependency on someone else's uptime and headers (Third-Party Scripts and the Supply Chain).

A backend-for-frontend on the app origin

when The client needs several services shaped for it, and you want one origin and one auth story (Backend for Frontend).

cost A service to build, deploy and operate — and one more hop of latency.

How to build it

Most important first.

  • Decide origin topology deliberately and early. Same-origin API and app is the simplest possible configuration and removes an entire category of problem; a separate API origin is a real choice with real costs (How API Shape Drives UI Complexity).
  • If you need cross-origin access, treat it as a server-side configuration task, not a frontend workaround. The server decides; the frontend can only ask (CORS).
  • Use a development proxy that preserves origin semantics rather than a browser flag that removes them, so that development and production behave the same.
  • Assume storage is partitioned. Anything relying on a third-party context sharing state with a first-party context is on a path browsers are actively closing (Storage Security and Durability).

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • Cross-origin iframes are opaque to the containing page and, for assistive technology, are a context switch. The embedded document needs its own title and language; the parent cannot supply them.
  • A third-party widget in an iframe cannot participate in the parent's focus order in the way an inline component can, and keyboard users notice the discontinuity even when nothing is broken.
  • When a cross-origin failure degrades a feature, the degradation must be announced, not merely rendered as an empty region (Live Regions and Announcement).

What can go wrong

Failure modes
  • A CORS error that is actually a server error: the request failed, so no CORS headers were sent, so the browser reports the CORS failure and hides the real status (Debugging the Network).
  • A preflight added silently by a custom header or content type, doubling latency on a hot path.
  • Credentials not sent cross-origin because the request did not ask for them, or rejected because the server used a wildcard origin with credentials.
  • Development working through a proxy and production failing, because the proxy hid the boundary rather than modelling it.
  • Assuming localhost and 127.0.0.1 are the same origin. They are not.
Security
  • This is the frontend's foundational security property: origin isolation is what makes it safe to visit an unknown page at all.
  • CORS is not authentication and does not protect your API. It governs whether a browser exposes a response to script; a non-browser client is unaffected and always was (CORS).
  • The same-origin policy does not stop a request from being *sent*. Cross-site request forgery exploits exactly that gap, which is why CSRF defences are separate (Cross-Site Request Forgery).
  • Anything you embed cross-origin extends your attack surface to that origin's operational security (Third-Party Scripts and the Supply Chain).
Misreads
  • "CORS protects my API." It does not. It is a browser policy about exposing responses to script, and it stops nothing that is not a browser (CORS).
  • "The same-origin policy stopped the request." Usually the request was sent and the response was withheld — which is exactly why CSRF is a separate problem.
  • "Subdomains are the same origin." They are not. app.example.com and api.example.com are different origins.
  • "It works with the browser security flag off, so the code is right." It is not; the code has been validated against a browser nobody uses.

Measuring it, and what changes in the field

How you would see this
  • The Network panel shows preflights as separate OPTIONS entries — the fastest way to discover you added one by accident.
  • The console distinguishes the CORS failure modes, and the distinction is the fix: missing header, wildcard with credentials, disallowed method, disallowed header.
  • CSP violation reports show what the policy blocked in the field, including the third-party additions nobody told you about (Content Security Policy).
Slow device, slow network, large data, old tab
  • Preflight cost is a round trip, so it hurts most on high-latency networks and is nearly invisible on a local one — a classic "works on my machine" asymmetry.
  • Storage partitioning behaviour differs between browsers and is tightening over time; anything depending on cross-site storage should be treated as already deprecated.
  • Embedded third-party content behaves differently in private browsing and with tracking protection enabled, which is a substantial fraction of real traffic.
What this costs
  • Serving the API from the app's origin removes CORS and preflight entirely, and couples deployment of the two.
  • A separate API origin gives independent scaling and clean separation, and costs a preflight, a CORS configuration and a cookie discussion.
  • Strict isolation headers unlock capabilities and lock out third-party embeds; that is a product decision, not a configuration detail.

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALThe origin model and the same-origin policy are specified and consistent across browsers; the differences are in the edges, not the rule.
  • BROWSER-SPECIFICStorage partitioning and third-party cookie behaviour differ substantially right now: Safari partitions aggressively by default, Firefox partitions by default in strict mode, Chromium is mid-transition. Any feature depending on third-party state has a different fate in each.
  • SPEC-EVOLVINGCross-site storage, cookie defaults and isolation requirements are actively changing. Treat any current behaviour here as a snapshot and verify against the specifications rather than against a tutorial.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

OS & Networkingdns-basics