What is an origin?
An origin is three things taken together: the scheme, the host name and the port. https://spokeandchain.example is one origin. http://spokeandchain.example, with plain http, is a different one. So is https://book.spokeandchain.example, a subdomain, and https://spokeandchain.example:8443, a different port. The path does not count: /api/account and /hire on the same host share an origin.
Browsers use the origin as the boundary for what a page's scripts may touch. A script loaded by a page on https://spokeandchain.example can read that origin's pages, storage and API answers freely. When it tries to reach into another origin, the browser applies the same-origin rule, usually called the same-origin policy.
https://spokeandchain.example/hireandhttps://spokeandchain.example/api/account: same origin.https://spokeandchain.exampleandhttp://spokeandchain.example: different scheme, different origin.https://spokeandchain.exampleandhttps://book.spokeandchain.example: different host, different origin, same site.https://spokeandchain.exampleandhttps://evil.example: different origin and different site.
What does the same-origin rule block?
The core of the rule is about reading. A script on evil.example can ask the browser to fetch https://spokeandchain.example/api/account. The browser sends the request, the shop's server answers, and then the browser refuses to give that answer to the script. The page sees a network error and an empty result.
That one refusal protects a great deal. Without it, any site you visited could read your webmail, your bank balance and the bike shop's account page, using your own signed-in browser. The rule also keeps evil.example out of the shop's localStorage, out of the DOM of a shop page it opens in a frame or a new window, and away from the shop's cookies through document.cookie.
You saw this in the first step of the task. The attack was blocked although you changed nothing, and the reason pointed out that the request still reached your server. Keep that detail in mind, because it leads straight to the gaps.
What does the same-origin rule allow?
The web was built on pages pulling in things from other places, and the rule leaves most of that alone. Any site can show your images, load your public scripts and stylesheets, link to your pages, embed your pages in a frame, and submit a form to any address. These are all ways of sending or displaying, and none of them let the other site read your content with its own code.
Two of those are what attackers use. A form on evil.example that posts to /account/email is sent by the browser, with any cookies the browser decides to attach. The attacker cannot read the reply, but if the request changes something, the damage is done before any reply exists. That is cross-site request forgery.
Framing works the same way. evil.example cannot read the shop page inside its frame, but it can make the frame invisible and place it over a button of its own, so a click meant for a game lands on Book this bike. That is clickjacking. Both have their own lessons. Here the point is that the same-origin rule was never designed to stop them, so you add separate defences: SameSite on the cookie, CSRF tokens, and a header that controls framing.
# headers.txt
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
X-Frame-Options: DENY

How is a site different from an origin?
Browsers use two boundaries, and mixing them up causes real bugs. An origin is the exact scheme, host and port. A site is wider: the registrable domain plus the scheme, so https://spokeandchain.example, https://book.spokeandchain.example and https://api.spokeandchain.example are three origins but one site.
The same-origin rule uses origins. SameSite cookies use sites. That is why a Lax cookie still travels from book.spokeandchain.example to spokeandchain.example, while a script on the booking subdomain still cannot read the main site's API answers without CORS. It also means any subdomain you hand to someone else, such as a blog on a hosted platform, is inside your site boundary for cookies.
The lab has one site, one attacker and no subdomains, so it never shows this difference directly. Real setups with several subdomains need to be checked with both boundaries in mind.
Why does the browser block the answer instead of the request?
The browser cannot know whether a cross-origin request is welcome until the server answers. Plenty of APIs are meant to be used from other sites. The answer carries headers saying who may read it, so the browser has to send the request, look at the reply's headers, and only then decide whether the calling script gets to see it.
For requests that look unusual, such as a PUT, a DELETE, or a POST with a JSON body, the browser first sends a small question called a preflight, an OPTIONS request, and only sends the real request if the server says yes. Ordinary form posts and simple GET requests skip the preflight because forms could always do them. Your server must therefore never assume a cross-origin request did not arrive just because a script could not read the result.
How do you see the same-origin rule in DevTools?
Open the Console on any page and run a fetch to another site's API that does not allow it. The Console shows a red message saying the request was blocked by CORS policy because no Access-Control-Allow-Origin header was present. In the Network tab the request is listed, and Chrome shows its status as CORS error even though the server did answer. That is the rule in action: request sent, answer withheld.
To check framing, look at the response headers of your page for X-Frame-Options or Content-Security-Policy with frame-ancestors. To check cookies, use Application then Cookies and read the SameSite column.
What is CORS and how does it relate?
Cross-Origin Resource Sharing is the way a server loosens the same-origin rule on purpose. By sending Access-Control-Allow-Origin and related headers, the shop's API can tell the browser that a particular other origin may read its answers. CORS can only open doors the rule closed. It cannot make anything stricter.
That makes CORS headers some of the most sensitive lines you will write. The break-it task below shows what happens when a server copies any visitor's Origin straight into the allow header. The next lesson covers CORS properly.
What do AI tools get wrong about the same-origin rule?
Faced with a CORS error in the console, assistants very often suggest switching the rule off for everyone: Access-Control-Allow-Origin: *, the cors() middleware with no options, or a line that reflects whatever Origin arrives. The error goes away, and so does the protection for any route that uses cookies. The right first question is which exact origin needs access, and whether it needs cookies at all.
They also describe the same-origin rule as if it blocked cross-site requests from being sent, which leads people to skip CSRF protection. It blocks reading, and a forged form only needs sending. Finally, several still produce X-Frame-Options: ALLOW-FROM, which modern browsers ignore. When a page must be framed by one partner, use frame-ancestors in a Content Security Policy header.
Field notes
- An origin is the scheme, host and port together, such as
https://spokeandchain.example. - Scripts from one origin cannot read answers from another origin.
- Other sites can still send requests, load images and frame your page.
- A site is wider than an origin, which matters for SameSite cookies.
Easy to get wrong
The rule stops other sites reading your answers. It does not stop them sending requests, and a forged form only needs the request to arrive.
ALLOW-FROM is obsolete and ignored, so the page can be framed by anyone. frame-ancestors is the current way to name allowed framers.
Reflecting the Origin header allows every site, including evil.example, to read the response.
Questions people ask
Does the same-origin rule protect my server?
No. It runs in the visitor's browser and protects the visitor's data on other sites. Tools like curl ignore it entirely. Your server still has to check every request for a valid session and permission.
Are subdomains the same origin?
No. book.spokeandchain.example and spokeandchain.example are different origins, so scripts cannot read each other's answers without CORS. They are the same site, which matters for SameSite cookies.
Why can my page load images from anywhere but not fetch JSON?
Showing an image does not let your script read its bytes. Fetching JSON would, so the browser requires the other server to agree through CORS headers.
Does the lab model subdomains?
No. It has one site and one attacker at evil.example. Real sites with several subdomains need checking with both the origin and the site boundary in mind.