The browser security model
Learn how origins, site isolation, timer limits, COOP and COEP, and V8 hardening keep browser scripts and data apart.
- 01Compare two addressesUse the scheme, host, and port to say whether two addresses have the same origin, and tell that apart from the same site.
- 02Name the wallsExplain how the same-origin policy, Chrome's separate processes, less exact timers, and the walls inside V8 each protect you, at different levels.
- 03Turn on isolation with careKnow when to send the COOP and COEP headers, what they unlock, and why page code still checks before it uses a tool.
Security is several walls
A browser runs code from many websites at the same time. You may have a shop, your bank, an advert, and a video open together. You did not write most of that code. So the browser must keep each website's code away from the other websites' private data.
It does this with several walls, not one. If one wall has a hole, the next wall still stands.
The browser security model is the set of walls a browser builds between websites:
- The same-origin policy: a rule that stops one website's JavaScript from reading another website's page or saved data.
- Separate processes: each website's pages can run as a separate program, with its own memory.
- Less exact timers: very exact timers can leak secrets, so the browser rounds them.
- Two response headers, COOP and COEP: a server can send them to ask for a stronger wall.
- Engine walls: checks inside V8, the program that runs JavaScript in Chrome.
Each wall works at a different level. One wall does not replace another. This lesson goes through them one at a time.
This lesson follows Realms, globals & WindowProxy. There you saw that each frame has its own global object. Here you will see the rules for what one frame may read from another. The next lesson, Load, link, evaluate, goes back to how modules run.
The same-origin policy
An origin is the first three parts of a web address (a URL):
- The scheme, like
https. - The host, like
shop.example. - The port, like
443. A port is a numbered door on the server. If an address shows no port,httpsuses 443 andhttpuses 80.
The path, like /cart, is not part of the origin. Two addresses have the same origin only when all three parts match.
The same-origin policy is the browser rule built on this. JavaScript from one origin cannot read the page, the saved data, or the server replies of another origin.
The code below uses two tools. new URL(text) turns an address string into a URL object. The object's origin property is a string with just the scheme, host, and port, like "https://shop.example".
const a = new URL("https://shop.example/cart");const b = new URL("https://shop.example:443/checkout");console.log(a.origin === b.origin); console.log(a.origin === new URL("http://shop.example").origin);console.log(a.origin === new URL("https://api.shop.example").origin);In plain words, line by line:
- Line 1 makes a URL for the shop's cart page.
- Line 2 makes a URL for the checkout page. It writes the port, 443, out in full.
- Line 3 prints
true. 443 is the porthttpsuses anyway, so the origins match. The different paths do not count. - Line 5 prints
false.httpis a different scheme fromhttps. - Line 6 prints
false.api.shop.exampleis a different host fromshop.example.
/cart and /checkout on the same shop have the same origin.The policy does not block everything between origins. A page can still link to another origin, send a form to it, or show its images. What it cannot do is read another origin's protected things:
- its page content (the DOM),
- its saved data (storage, like
localStorage), - its server replies, unless that server agrees.
When two origins want to share on purpose, there are two safe tools. CORS lets a server say "this other origin may read my reply". postMessage lets two windows send each other messages.
Picture a block of flats. Every flat has its own lock. Your key opens your flat and no other. A flat in another building, on another floor, or with another door number needs a different key, even if it looks a lot like yours.
- In real life: Your key opens your own flat
- In JavaScript: A script can read data from its own origin
- In real life: A flat in another building needs another key
- In JavaScript: A different host is a different origin
- In real life: A flat on another floor needs another key
- In JavaScript: A different scheme is a different origin
- In real life: A flat with another door number needs another key
- In JavaScript: A different port is a different origin
Step through a comparison of two shop addresses, one line at a time. It runs the lesson's own sameOrigin function.
script
const second = "https://shop.example:443/checkout"; function sameOrigin(first, second) { const a = new URL(first); const b = new URL(second); return a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;} console.log(sameOrigin(first, second));Same origin and same site answer different questions
A site is a bigger group than an origin. Roughly, it is the scheme plus the main domain name: the part an owner registers, like shop.example. Two addresses are the same site when those match.
So shop.example and api.shop.example are the same site. They are still different origins, because their hosts are different. The browser uses "same site" for some rules, like when to send cookies. It uses the stricter "same origin" to decide whether a script may read another page.
function sameOrigin(first, second) { const a = new URL(first); const b = new URL(second); return a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;} console.log(sameOrigin( "https://shop.example/cart", "https://shop.example:443/checkout",));https | shop.example | 443Its scheme, host, and port. This address stays https://shop.example/cart.
https | api.shop.example | 443Its scheme, host, and port. They come from the address you type.
falseTrue only when the scheme, host, and port all match.
trueTrue when the schemes match and the last two parts of each host match.
Scheme: same. Host: different. Port: same. Not all three match, so the two addresses are different origins. By the lesson's simple rule, they are the same site.
The first address stays https://shop.example/cart. You change the second one. Try these:
- Change only the path. The origin stays the same.
https://shop.example:443: still the same origin, becausehttpsuses 443 anyway.http://shop.example: a different origin, because the scheme changed.https://api.shop.example: a different origin, because the host changed. It is still the same site.
Here is a small model of "same site", written for this lesson.
function sameSite(first, second) { const a = new URL(first); const b = new URL(second); const site = (url) => url.hostname.split(".").slice(-2).join("."); return a.protocol === b.protocol && site(a) === site(b);} console.log(sameSite( "https://shop.example/cart", "https://api.shop.example/orders",));In plain words, line by line:
- Line 1 starts a function,
sameSite, that takes two addresses. - Line 2 turns the first address into a URL object.
- Line 3 does the same for the second address.
- Line 4 makes a small helper,
site. It keeps only the last two parts of the host.split(".")cuts the host at each dot,slice(-2)keeps the last two pieces, andjoin(".")glues them back. Soapi.shop.examplebecomesshop.example. - Line 5 returns
truewhen the schemes match and the two short hosts match.
Lines 8 to 11 call it with the shop address and the API address. It prints true.
Real browsers do not just keep the last two parts. Some endings, like co.uk, are shared by many different owners. So browsers use a list of these shared endings, called the Public Suffix List. Use this model only for learning, never in real code.
shop.example and both use HTTPS, so the lesson's small model calls them the same site.The table below compares three terms. Each row names one term. Read across to see what must match and which question the term answers.
| Term | What must match? | What question does it answer? |
|---|---|---|
| Same origin | The scheme, host, and port all match. | May this page's script read the other page's data? |
| Same site | The scheme and the main domain name match, like shop.example. | Should the browser send cookies, and may Chrome keep both pages in one process? |
| Cross-origin isolated | The server sent both the COOP and COEP headers. | May this page use a few powerful tools, like shared memory? |
Step through the lesson's small same-site model, one line at a time. It shows the idea of a site, not the full Public Suffix List rule.
script
const second = "https://api.shop.example/orders"; function sameSite(first, second) { const a = new URL(first); const b = new URL(second); const site = (url) => url.hostname.split(".").slice(-2).join("."); return a.protocol === b.protocol && site(a) === site(b);} console.log(sameSite(first, second));Separate processes for separate sites
The same-origin policy is a rule, and browser code checks it. But browser code can have bugs. So browsers add a second wall that does not depend on that check.
A process is a running program with its own memory. One process cannot read another process's memory. A renderer process is the process that draws a page and runs its JavaScript.
Chrome has a feature called Site Isolation. It puts pages from different sites into different renderer processes. Each of these processes also runs in a sandbox: it is locked down, so it cannot touch your files or other programs directly.
const page = "https://shop.example";const frame = "https://news.example"; console.log("same site?", sameSite(page, frame));console.log("same origin?", sameOrigin(page, frame));console.log("Chrome isolates cross-site pages in separate renderer processes");This code uses the sameSite and sameOrigin functions from earlier in the lesson, so it is shown here without a Run button.
- Line 1 names a page on
shop.example. - Line 2 names a frame on
news.example. - Line 4 prints
same site? false. The main domain names are different. - Line 5 prints
same origin? false. The hosts are different. - Line 6 only prints a sentence. JavaScript cannot check processes. The sentence says what Chrome does.
shop.example and news.example are different sites, so their pages run in two separate processes. Each process has its own memory, so one site's data is not even there for the other to read.For this wall, Chrome groups pages by site, not by origin. It ignores subdomains and ports. Some older websites let pages on two subdomains of one site work with each other directly. To keep those websites working, Chrome keeps one site's pages together. Pages from different sites still end up apart. The lesson Browser architecture explains the browser process, renderer processes, and sandboxes in more depth.
Site Isolation does not replace the same-origin policy. It is an extra wall. Suppose an attacker finds a bug that gets past the same-origin check. The other site's private data is still not in the attacker's process, so it is much harder to steal.
Spectre and less exact timers
Spectre is a kind of attack on the computer's processor (the CPU). The attacker never reads the secret directly. Instead, the attacker measures how long tiny steps take. Small differences in time can give away pieces of data.
This is called a side channel: the secret leaks out through a side effect, here time, instead of through a normal read. Browsers fight it in two ways:
- They keep different sites in separate processes.
- They make timers less exact on normal pages.
const isolatedResolution = 0.005;const regularResolution = 0.1; console.log("isolated context microseconds:", isolatedResolution * 1000);console.log("regular context microseconds:", regularResolution * 1000);In plain words, line by line:
- Line 1 stores
0.005milliseconds: the smallest timer step on an isolated page. - Line 2 stores
0.1milliseconds: the smallest timer step on a normal page. - Lines 4 and 5 multiply by 1,000. That turns milliseconds into microseconds (millionths of a second). They print
5and100.
MDN, the main reference site for web developers, lists these two numbers. They are what Chrome does. Other browsers can choose different numbers. "Isolated" here means cross-origin isolated; you will meet that in the next section.
Picture neighbours who share a thin wall. Someone listens very carefully. By timing tiny sounds, they guess what the neighbour is doing. The building does two things. It gives everyone clocks with fewer marks, so nobody can time sounds exactly. And it moves neighbours with secrets into separate buildings.
- In real life: Someone times tiny sounds through a thin wall
- In JavaScript: An attacker measures tiny differences in time
- In real life: Everyone gets clocks with fewer marks
- In JavaScript: The browser makes timers less exact
- In real life: Neighbours with secrets move to separate buildings
- In JavaScript: Site Isolation puts different sites in separate processes
performance.now() gives the time in milliseconds since the page started. Do not expect it to be equally exact everywhere. It is fine for measuring how fast your code runs. But do not build security that depends on tiny time differences.
COOP, COEP, and cross-origin isolation
Cross-origin isolation is a stronger state that a page can ask for. It is off unless the page asks. In return, the page may use a few powerful tools that are too risky on normal pages.
To ask for it, the server sends two response headers. A response header is a short line of text the server sends along with the page, before the page's content.
// Response headers. The server sends them with the page.Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp // Files from other origins must agree, with CORP or CORS.In plain words, line by line:
- Line 1 is a note: the server sets these headers.
- Line 2 is COOP, short for Cross-Origin-Opener-Policy. With
same-origin, a popup window from another origin can no longer reach this page, and this page cannot reach it. Only windows from the same origin stay connected. - Line 3 is COEP, short for Cross-Origin-Embedder-Policy. With
require-corp, the page may load a file from another origin (an image, script, font, or frame) only if that file agrees.
A file agrees in one of two ways. It sends its own CORP header (Cross-Origin-Resource-Policy) saying other origins may use it. Or the page loads it with CORS, and its server allows that. Line 5 is a note that says the same thing.
When both headers are set, the browser sets a global variable, crossOriginIsolated, to true. One more header, the permissions policy, can turn features off; it must not block this one. A true value can unlock these tools:
SharedArrayBuffer: memory that two threads can use at the same time.- More exact timers.
performance.measureUserAgentSpecificMemory(): a way to measure how much memory the page uses.
Not every browser has every one of these tools. So check that a tool exists before you use it. This is called feature detection.
false. When the server sends both headers, the page gets true. From then on, files from other origins load only if they agree.const isolated = globalThis.crossOriginIsolated === true;console.log("cross-origin isolated:", isolated); if (isolated && "SharedArrayBuffer" in globalThis) { console.log("shared memory is available");} else { console.log("use an ordinary ArrayBuffer here");}In plain words, line by line:
- Line 1 reads
crossOriginIsolatedfromglobalThis, the global object. The=== trueturns it into a plaintrueorfalse, even if the variable does not exist. - Line 2 prints that value.
- Line 4 checks two things: the page is isolated, and
SharedArrayBufferexists. - Line 5 runs only when both are true.
- Line 7 runs in every other case. It uses a normal
ArrayBuffer: memory for one thread only.
On a normal page, it prints cross-origin isolated: false and then use an ordinary ArrayBuffer here.
Turning on COEP can break a page. Images, scripts, fonts, or frames from other origins stop loading if they do not agree. So go step by step:
- Start in report-only mode. The browser then only reports what would break, and blocks nothing.
- List every file your page loads from other origins.
- Ask each one to send CORP, or load it with CORS.
The CORS and Windows and frames lessons show the related tools for pages.
Walls inside the JavaScript engine
V8 is the JavaScript engine in Chrome: the program that runs your JavaScript. V8 itself is written in C++. A bug in V8 can let an attacker write over memory they should never touch. This is called memory corruption.
The V8 sandbox is a wall inside V8. V8 keeps its heap, the memory where JavaScript objects live, inside one fenced-off region. If a bug corrupts memory there, it is hard to reach memory outside the region. This all happens inside the engine. Your page cannot turn it on or off, and no header controls it.
// A short summary in plain words, not real V8 code.const protections = [ "V8 objects stay inside one fenced-off memory region", "addresses outside the region go through a checked table", "machine code is never writable and runnable at once",]; console.log(protections.length);In plain words, line by line:
- Line 1 is a note: this is a summary, not real V8 code.
- Line 2 starts a list of three walls.
- Line 3: V8's objects stay inside a fenced-off memory region.
- Line 4: when something in the region points to memory outside it, V8 looks the address up in a checked table. It never follows a raw address.
- Line 5: memory that holds machine code made while the program runs can be written, or run, but never both at the same time.
Line 8 would print 3, the number of items in the list.
Line 5 needs one more word. JIT is short for just-in-time compiling. While your program runs, V8 turns busy JavaScript into machine code, the CPU's own instructions, to make it faster. If an attacker could write into that machine code, they could make the CPU do anything. So V8 never lets that memory be writable and runnable at once. This is called JIT hardening.
The V8 sandbox does not stop every V8 bug. It makes one bug not enough. To escape the region, an attacker needs a second bug that breaks another wall.
- The same-origin policy keeps one origin's page and data away from other origins.
- Site Isolation, in Chrome, keeps different sites in different processes.
- The V8 sandbox keeps a V8 memory bug inside one region of a process.
All three protect you, but they work at different levels.
What this means for your website
Most web developers work with origins, CORS, messages between windows, and headers. Browser makers build the lower walls: processes, timers, and the V8 sandbox. It still helps to know them, because a security report may mention a renderer process, a timer, or cross-origin isolation. First find which wall is missing. Then pick the fix for that wall.
- If an app needs a real wall around it, give it its own origin, like its own subdomain. A different path, like
/admin, is the same origin, so it is not a wall. - To talk to another window or frame, use
postMessageand name the exact origin that may receive the message. - Turn on CORS only on a server that really wants another origin to read its replies.
- Before you turn on COEP, list every image, font, script, frame, and worker your page loads from other origins.
- Check
crossOriginIsolated, and check that each tool exists, instead of guessing what the browser supports.
https://shop.example:8443is not the same ashttps://shop.example.- A script on
shop.examplewants to read a page onapi.shop.example. - Chrome runs pages from different sites in separate processes.
- A browser makes it hard to measure tiny time differences.
- The server sends
Cross-Origin-Opener-Policy: same-originwith the page. - The server sends
Cross-Origin-Embedder-Policy: require-corpwith the page.
Put each card under the rule it describes best. After each card, read why it belongs there.
The table below lists the walls. Each row is one wall. Read across to see what it stops and what you, the developer, do about it.
| Wall | What does it stop? | What do you do? |
|---|---|---|
| Same-origin policy | It stops a script from reading another origin's page, saved data, and most server replies. | Use CORS or postMessage when two origins should share on purpose. |
| Site Isolation in Chrome | It runs pages from different sites in separate, locked-down processes. | Nothing to call. Chrome does it for you; JavaScript cannot turn it on. |
| Less exact timers | It rounds timers, so tiny time differences are harder to measure. | Do not build security on a timing difference you saw once. |
| COOP and COEP | When both are set, they make the page cross-origin isolated. | Make sure every file from another origin agrees, with CORP or CORS. |
| V8 sandbox and JIT hardening | It keeps a memory bug inside V8's own region, and keeps machine code from being changed. | Nothing to set. These are walls inside the engine, not headers. |
Common mistakes about browser security
- "Pages on the same site can read each other." No. To read another page directly, a script normally needs the same origin, not just the same site.
- "CORS turns off the same-origin policy." No. CORS lets one server allow certain other origins to read its replies. Everything else stays blocked.
- "COOP and COEP are JavaScript settings." No. They are response headers. The server sends them before any of the page's JavaScript runs.
- "Every browser puts every site in its own process." No. Site Isolation is a Chrome feature, and how it works depends on the device and the platform.
- "The V8 sandbox means bugs in my page do not matter." No. It is one wall inside the engine. Your page code and your server checks still need to be safe.
The table below lists terms that people often mix up. Read across each row: what the term means, and what it is not.
| Term | What does it mean? | What is it not? |
|---|---|---|
| Same site | A bigger group that some browser rules use, like the cookie rules. | Same origin. Two pages can be the same site with different subdomains or ports. |
| CORS | A way for a server to let certain other origins read its replies. | A way to make two origins into one origin. |
| Site Isolation | Chrome running different sites in separate processes. | A promise that every address gets its own process in every browser. |
crossOriginIsolated | A true-or-false value you read to see if this page is isolated. | A header that JavaScript can set after the page has loaded. |
When you look at a security problem, ask two questions:
- What is the attacker trying to read or control?
- Which wall is meant to stop that?
Then you will not use a header to fix a CORS problem, or blame a process for an origin problem.
Practice exercises
Read the code. Type what it prints.
const a = new URL("https://shop.example/cart");
const b = new URL("https://shop.example:443/checkout");
console.log(a.origin === b.origin);It prints true. The scheme, host, and port all match, because 443 is the port https uses anyway. The paths do not count.
What does the comparison print?
const secure = new URL("https://shop.example");
const plain = new URL("http://shop.example");
console.log(secure.origin === plain.origin);It prints false. The host is the same, but the scheme is different, so the origin is different.
An origin has three parts: scheme, host, and port. Which part is different here?
const shop = new URL("https://shop.example");
const api = new URL("https://api.shop.example");
console.log(shop.origin, api.origin);The host (also called the hostname) changed. shop.example and api.shop.example are different hosts, so they are different origins.
Your app needs cross-origin isolation so it can use shared memory. Name the two response headers that usually turn it on.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpCOOP and COEP are response headers that the server sends. Under COEP, files from other origins still have to agree, with CORP or CORS.
What should real code check before it uses a tool that only works on isolated pages?
const isolated = false;
console.log(isolated ? "use SharedArrayBuffer" : "use ArrayBuffer");Check crossOriginIsolated. It tells this page, right now, whether it is cross-origin isolated.
A shop page loads images and scripts from other companies. What should the team test before turning on COEP?
First list every file loaded from another origin. Each one needs CORP or CORS. Try report-only mode, which only reports problems, before you turn on require-corp for real.
Check your understanding
For each question, first decide which wall it is about. Questions about addresses are about origins. The others are about processes, timers, response headers, or memory bugs in V8.
Question 1 of 7When do two normal web addresses have the same origin?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconst a = new URL("https://shop.example"); const b = new URL("https://shop.example:443"); console.log(a.origin === b.origin);Choose an answer to see the explanation.
Question 3 of 7What does Site Isolation in Chrome add on top of the same-origin policy?
Choose an answer to see the explanation.
Question 4 of 7Why do browsers make timers less exact on normal pages?
Choose an answer to see the explanation.
Question 5 of 7Which pair of headers usually turns on cross-origin isolation?
Choose an answer to see the explanation.
Question 6 of 7What does
crossOriginIsolatedtell a page?Choose an answer to see the explanation.
Question 7 of 7What is the V8 sandbox trying to keep in?
Choose an answer to see the explanation.
Key takeaways
- Two addresses have the same origin when the scheme, host, and port all match. The path does not count.
- Same site is a bigger group. It does not let scripts read each other's pages.
- In Chrome, Site Isolation runs different sites in separate processes, as an extra wall.
- Less exact timers and separate processes make Spectre-style timing attacks harder.
- The COOP and COEP headers can make a page cross-origin isolated. Check
crossOriginIsolatedbefore you use tools that need it. - The V8 sandbox and JIT hardening protect the engine itself. They add to the same-origin policy; they do not replace it.
Remember the one-liner.
Browser security works because several separate walls keep one website's code and data away from other websites.
Coming next: Load, link, evaluate. You will follow a group of modules through the three steps the browser uses to run them.