Notes · Dissecting Real Systems
growing
Code From Strangers
A map of web security before the deep dives — one asymmetry that explains every attack, five questions the mechanisms answer, and the vocabulary you need so none of the rest reads as noise.
Where feasible, a protection mechanism that requires two keys to unlock it is more robust and flexible than one that allows access to the presenter of only a single key… From then on, no single accident, deception, or breach of trust is sufficient to compromise the protected information.
— Jerome H. Saltzer and Michael D. Schroeder, The Protection of Information in Computer Systems (Proceedings of the IEEE 63(9), 1975)
Cite this
Mangalapilly, Y. J. (2026, August). Code From Strangers. Saṃhitā Notes. https://yesudeep.com/blog/code-from-strangers/ @online{mangalapilly2026code,
author = {Yesudeep Jose Mangalapilly},
title = {Code From Strangers},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {August},
url = {https://yesudeep.com/blog/code-from-strangers/},
urldate = {2026-08-12},
} Yesudeep Jose Mangalapilly. “Code From Strangers.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/code-from-strangers/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - Code From Strangers
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/code-from-strangers/
Y2 - 2026-08-12
ER - A map, before the territory. The opening article maps the web-security series, and it is the only article that does not dissect a single mechanism in depth. By the end you'll have the vocabulary the later pieces assume, the one asymmetry that explains why the attacks take the shapes they do, a five-question taxonomy for placing any mechanism you meet, an honest account of what "defense in depth" does and does not buy, what a single third-party script grants and what that has cost the companies that granted it, and a picture of the standards work active in 2026 — which is not what most coverage suggests.
Start with the arrangement itself.
You visit a page. The browser fetches HTML from a server you have never audited, discovers references to scripts on three other servers, fetches those too, and executes all of it — with access to your cookies, your session, your camera permission prompt, and the memory of the tab. Page startup happens in a few hundred milliseconds, thousands of times a day, and it is normal.
It works because the browser maintains a boundary. Understanding that boundary, and precisely where it does not hold, is most of what web security is.
Two properties of that boundary explain why the field looks the way it does, and why the web's security is presently in the state it is in.
The boundary runs between origins, not inside one. Once a script is running in your page it is your page: the same cookies, the same form fields, the same ability to call your own API as the signed-in reader. The browser draws no line between the code your team wrote and the code your team included. There is no such thing as a less-privileged script.
And the permissive behavior is the default. Every mechanism in this series is something a site switches on. A page that sends no security headers gets the platform's original semantics, and those semantics are: run it. The browser cannot simply reverse that, because a default that broke every page nobody had updated would not be the web — the same reason the form encodings further down stayed grandfathered. So the correction arrives opt-in, one header at a time, and whether any given page is protected is a decision somebody has to make, fund, and maintain.
Mostly the decision is not made. CSP reaches 21.9% of mobile pages, and about 92% of those policies keep unsafe-inline, which is the part that would have constrained script execution. The mechanisms are not missing. They are off.
The asymmetry everything rests on
The boundary is the same-origin policy, and it is almost always described imprecisely. It does not stop one origin from talking to another. It stops one origin from hearing the reply.
Origin — the tuple of scheme, host, and port: https://example.com:443. Change any of the three and it is a different origin. Site is looser — scheme plus registrable domain — so a.example.com and b.example.com are different origins but the same site.
MDN sorts cross-origin access into three cases, and the asymmetry is right there in the verbs:
- Cross-origin writes — links, redirects, form submissions: "Typically allowed."
- Cross-origin embedding — images, scripts, stylesheets, frames: "Typically allowed."
- Cross-origin reads — the response body: "Typically disallowed."
Sit with what that permits. A page on evil.example can submit a form to your bank, with your cookies attached, and the bank will process it. What evil.example cannot do is read the response. So an attacker designs an attack that does not need to read the response — and that attack has a name.
Cross-site request forgery (CSRF) — an attack that "forces an end user to execute unwanted actions on a web application in which they're currently authenticated." The attacker cannot see the result, and does not need to: the transfer, the password change, or the deletion has already happened. (OWASP)
OWASP states the logic of the constraint exactly:
Forcing the victim to retrieve data doesn't benefit an attacker because the attacker doesn't receive the response, the victim does.
CSRF is not a hole in the same-origin policy. It is the same-origin policy working as specified, plus an attacker who arranged not to need the part it withholds.
Now consider the opposite move. If sending is easy but reading is refused, the valuable position is inside the origin — because code running there reads freely. That is the entire prize of the other great attack class.
Cross-site scripting (XSS) — "a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites." The script runs with the origin's full authority: its cookies, its storage, its permissions. (OWASP)
The usual taxonomy is three-way — reflected (the payload bounces off the server in a response), stored (the server persists it), and DOM-based (the payload never reaches the server at all; client-side code writes it into the page). Worth knowing that OWASP's own model is really a 2×2 of stored/reflected against server/client, with DOM-based a subset of client XSS. The three-way version is a simplification, and a serviceable one.
The building has a rule: you may push notes under any door, and you may knock on any door, but you may not hear what is said inside. Two strategies follow. Write a note that makes something happen without needing a reply — that is CSRF. Or get yourself inside the room, where listening is allowed — that is XSS. Nearly every attack in this series is a variation on one of the two.
Where CORS fits, and the thing everyone gets backwards
Since reads are refused by default, there has to be a way to permit them deliberately — otherwise no API on a different origin could ever be called.
CORS (Cross-Origin Resource Sharing) — "an HTTP-header based mechanism that allows a server to indicate any origins… other than its own from which a browser should permit loading resources." (MDN)
Read the verbs: allows, permit. CORS is a relaxation of the same-origin policy, not a restriction added on top of it. A server sending no CORS headers is in the most restrictive state available, not an unprotected one. The number of times "we need to fix our CORS" means "we need to loosen it" is a good indication of how widely this is misread.
Preflight — for requests that could have side effects, the browser first sends an OPTIONS request "in order to determine if the actual request is safe to send." Triggered by methods other than GET/HEAD/POST, by non-safelisted headers, and by content types outside the three a plain HTML form can produce. (MDN)
That last carve-out is not arbitrary. The exempt content types — application/x-www-form-urlencoded, multipart/form-data, text/plain — are exactly what an HTML form could already send before CORS existed. They are grandfathered because forbidding them would break the web, which is also precisely why CSRF remains possible against endpoints that accept form encodings.
What a third-party script costs
The decision that creates most of this exposure is rarely made by an engineer, and it never looks like a security decision. Someone needs analytics, a chat widget, a consent banner, a heatmap, a conversion pixel. The request is reasonable, the vendor is legitimate, and the integration is a snippet to paste before </body>.
What that snippet grants, in the browser's model, is everything the page can do: read what the reader types before it is submitted, read any cookie not marked HttpOnly, and issue requests as the signed-in reader. Not because the vendor is hostile — because, as above, the platform has no way to grant less.
Third-party script — JavaScript served by a domain you do not control, running inside your page's origin with your page's authority. Also sold as a tag, pixel, or snippet; the words describe the billing model, not the privilege. Learn more.
A poster in a shop window is a third-party image. It takes up space and can be looked at, and that is the end of its power. A third-party script is not a poster. It is a key — and the shop has only one key, which opens the till, the customer list, and the back office. Handing a supplier a copy is the integration. Most suppliers only ever open the front door. The key still fits everything.
The costs are a matter of public record.
| year | what was on the page | who was affected | recorded cost |
|---|---|---|---|
| 2018 | a supplier's chat bot, included on the payment page | 9.4 million EEA customers | £1.25m ICO penalty, Nov 2020 |
| 2018 | 22 lines of JavaScript an attacker added to the payment page | 429,612 people; ~244,000 card numbers with CVV | £20m ICO penalty, Oct 2020 |
| 2019–20 | advertising pixels on prescription and mental-health pages | GoodRx and BetterHelp users | $1.5m and $7.8m FTC orders, 2023 |
| 2024 | a CDN script already trusted, after its domain changed hands | 100,000+ sites | emergency removal across every one |
The Ticketmaster row is the one worth sitting with, because nothing at Ticketmaster was hacked. The chat bot was a real product from a real supplier, placed on the payment page by an ordinary product decision. The supplier was compromised, and the payment page inherited the compromise. The penalty notice records that staff were getting alerts about the chat bot months before anyone acted on them.
The British Airways figure needs one honest qualification: the injected script was the exfiltration route, and the same notice also found card data logged in plaintext since 2015. The penalty is not attributable to the script alone.
The polyfill.io row makes the point the other three do not. Nothing was added in 2024. The script had been on those pages for years, doing exactly what it promised, when the domain changed owners and began serving different code to the same tag. Approving a script is not a decision that completes. It is a standing grant, renewed on every page load, until somebody removes it.
The part that cannot be undone afterwards
A breach can be contained and a script can be deleted. The regulatory position is harder, because it is retrospective.
Under Fashion ID (C-40/17, 2019) the Court of Justice held that a site embedding a third-party social plugin is a joint controller with the plugin's provider for the collection and transmission of visitors' data — a retailer with a Like button, not a data broker. Embedding the tag is what creates the controllership. That means the lawful basis, the disclosure, and the consent had to exist before the first page load, and no later fix supplies them after the fact.
That is why the pixel cases resolved as they did. When The Markup found Meta's pixel on hospital patient portals in 2022, the data had already been sent. Removing the pixel stopped the next transmission and changed nothing about the previous ones. The FTC's own account of pixel tracking is worth reading for what it says about how routine the installations were.
Since 31 March 2025 the position is stricter still for anyone taking payments: PCI DSS v4.0 requirement 6.4.3 obliges an inventory of every script on a payment page with written justification, a method confirming each is authorized, and a method assuring its integrity; 11.6.1 obliges detection of unauthorized change. "Add it now and review it later" is a finding.
None of this is an argument against third-party scripts. It is an argument that the price is invisible at the moment the decision is made, and is paid later by someone who was not in the room. Two mechanisms in this series make the grant smaller rather than refusing it: Subresource Integrity pins the exact bytes, so a script that changes underneath you fails closed instead of executing; and CSP names which origins may execute at all. Both cost minutes at integration time and weeks to retrofit across a site that already has forty tags.
Five questions
Here is the map. Every mechanism in this series answers one of five questions, and the questions do not overlap.
| the question | who answers it | where it fails |
|---|---|---|
| May this connection be plaintext? | HSTS, upgrade-insecure-requests | an attacker on the network rewrites the response |
| May this code run? | CSP, Trusted Types | injected script executes with your origin's authority |
| May this request be honored? | fetch metadata, SameSite cookies | your server acts on a request it should have refused |
| May this data share my process? | COOP, COEP, CORP | a side channel reads memory the policy said was unreadable |
| Is this content, or is it code? | sanitizers, nosniff | data gets interpreted as markup or script |
Two things this table is for.
First, when you meet an unfamiliar acronym, place it. Referrer-Policy belongs to a sixth column about information disclosure; X-Frame-Options and frame-ancestors belong to the fourth. Having a slot to put something in is most of what stops a field from reading as noise.
Second, and more usefully: the questions are independent, which means the answers do not substitute for one another. A strict CSP does nothing about a plaintext connection. HSTS does nothing about injected script. The independence can sound obvious stated flatly, and it is routinely ignored in practice — which brings us to the phrase that does the ignoring.
"Defense in depth" is doing too much work
The standard advice is to layer defenses. NIST's glossary carries several definitions; one of them describes "the application of multiple countermeasures in a layered or stepwise manner."
The trouble with layering as a metaphor is that it invites counting. Four headers, therefore four layers, therefore better than two. But layers only add protection when their failures are independent — and nothing in the metaphor tells you whether yours are.
Jerome H. Saltzer and Michael D. Schroeder named the property that actually matters, fifty years ago, and did not call it layering. They called it separation of privilege: a mechanism needing two keys is stronger because "the two keys can be physically separated and distinct programs, organizations, or individuals made responsible for them. From then on, no single accident, deception, or breach of trust is sufficient to compromise the protected information."
The operative words are separated and distinct. What makes two controls worth two controls is that one failing does not imply the other failing. They also named the failure mode, as least common mechanism: "Minimize the amount of mechanism common to more than one user and depended on by all users." Shared mechanism is where correlated failure comes from.
Two controls resting on the same assumption are one control wearing two names.
COOP, COEP and CORP are the clean example, and the piece on them makes the case in full: they do not stack: they compose into a single property, and a site with two of the three correct is exactly as isolated as a site with none. Counting would have told you it was two-thirds of the way there.
Warning
The same structure explains why "we'll harden it progressively" is a weaker plan than it sounds, and why the last piece in this series argues the point at length. Adding mechanisms one at a time feels like accumulating protection; often it is accumulating mechanisms whose failures are correlated, while the shared assumption underneath goes unexamined.
What is actually deployed
A reality check, because the literature describes a web that does not exist. From the Web Almanac 2025, mobile pages:
| mechanism | adoption |
|---|---|
X-Content-Type-Options | ~50% |
| HSTS | 36% |
X-Frame-Options | ~35% |
| CSP | 21.9% |
Permissions-Policy | 3.7% |
| CORP | >2.25% |
| COOP | ~2% |
| COEP | ~0.75% |
The ordering is the finding. The two most-deployed headers are the two oldest and simplest — single-value, no configuration, essentially no way to break your site. The three that establish real isolation boundaries sit near two percent. Adoption tracks cost of deployment, and it tracks it far more closely than it tracks security value.
It is worse than the CSP number suggests, too. Among sites that do send a CSP, roughly 92% include unsafe-inline — so the fraction of the web with a policy that meaningfully constrains script execution is far below 21.9%.
Note
There is no "Learn Security" course on web.dev. There are structured courses for HTML, CSS, JavaScript, performance, privacy, accessibility, images, design, forms, PWA, testing and AI. Security is a curated link collection organized around what a compromised site owner searches for — three of its ten sections are incident response. MDN does better, splitting attacks from defenses and giving threat modeling its own heading. The absence of a canonical teaching sequence is part of why this material feels like word soup, and it is the gap this series is trying to fill.
What standards bodies are building in 2026
Worth knowing where the field is going, because the direction has changed and most coverage has not caught up.
Process maturity tells you nothing
Content Security Policy Level 3 is a Working Draft dated 29 July 2026. It has been a Working Draft for about a decade, while being deployed on a fifth of the web and being the mechanism every security guide leads with.
That is not dysfunction, it is a deliberate posture. The Web Application Security Working Group's charter said plainly that it "intends to publish the latest state of their work as Candidate Recommendation (with Snapshots) and does not intend to advance their documents to Recommendation." Referrer Policy's Candidate Recommendation is from January 2017. upgrade-insecure-requests has been a CR since 2015.
Important
Do not read W3C maturity levels as readiness signals. A Working Draft can be universally implemented and a Candidate Recommendation can be a decade stale. Check browser support and the spec's own edit history instead — for CSP3 the last substantive commit is the same date as the draft.
Worth noting a live contradiction: CSP3's own status section says "This document is intended to become a W3C Recommendation," which is the opposite of what the charter says. Both texts are current.
The work has moved to supply chain
The injection-era specs are largely static. Fetch Metadata has not been touched since April 2025. Mixed Content's last draft is 2023. Meanwhile:
- Integrity Policy — a mechanism to require Subresource Integrity for scripts and stylesheets rather than merely permitting it — had limited browser availability on 2026-08-12. Safari 26.0 added enforcement for script destinations.
- Signature-based SRI is in origin trial, using Ed25519 signatures so an integrity assertion can survive a legitimate change to the file.
- Subresource Integrity has been promoted in the proposed 2026 charter from a maintained Recommendation to a normative Level 2 deliverable.
Read together, the most active work concerns where the code came from, not what the code does. The injection problem is considered adequately specified; the delivery problem is not.
Defaults, not knobs
The other visible pattern is a move from opt-in headers to secure-by-default behavior. Chrome is making HTTPS the default — warning on public-site plaintext navigations from Chrome 147 in April 2026, and flipping the default in Chrome 154 in October 2026. Certificate lifetimes are ratcheting down by industry ballot: 200 days from March 2026, 100 days from March 2027, and 47 days from March 2029.
The interesting consequence for a site owner is that the ground moves under you whether or not you act. Several things this series recommends will eventually stop being recommendations and start being the way the browser behaves.
How to read the rest of this series
Each piece takes one mechanism and dissects it against its specification. They are written to be read in order, but they stand alone. Start with the piece that answers the question you face.
The last piece is the argument the others are evidence for — that the deny-by- default posture is not a stylistic preference but a structural claim, and that the incremental alternative fails for reasons you can state precisely. It is last because it is much more convincing once you have felt the specific mechanisms.
One habit worth carrying through all of them. When you meet a new mechanism, ask which of the five questions it answers, and then ask what else would have to be true for it to actually protect you. That second question is the one that separates a deployed header from a defended site.
Lessons
- The same-origin policy restricts reading, not sending. Writes and embeds are permitted; only the response body is withheld.
- CSRF and XSS are the two ways around that. One avoids needing the response; the other gets inside the origin where reading is allowed.
- CORS relaxes the policy, it does not tighten it. No CORS headers is the restrictive state.
- Five independent questions. Plaintext, execution, request honoring, process sharing, content-versus-code. Answering one says nothing about the others.
- Layering is the wrong metaphor; independence is the property. Two controls sharing an assumption are one control. Saltzer and Schroeder called it separation of privilege in 1975.
- Adoption tracks deployment cost, not security value. The simplest headers are the most deployed; the isolation headers sit near 2%.
- W3C maturity is not a readiness signal. CSP3 is a decade-old Working Draft deployed on a fifth of the web.
- A third-party script carries your page's authority, not less. The platform offers no reduced-privilege script, the grant renews on every load, and the recorded costs — ICO penalties, FTC orders, joint controllership under Fashion ID — land on whoever owns the page.
- The frontier is supply chain and defaults, not injection.
Practice
References
- MDN. “Same-origin policy.” MDN. — the read/write/embed taxonomy, in the platform's own words
- MDN. “Cross-Origin Resource Sharing.” MDN. — CORS as a relaxation mechanism, preflight triggers, and the safelisted content types
- Saltzer, Schroeder. “The Protection of Information in Computer Systems.” Proceedings of the IEEE 63(9), 1975. — separation of privilege and least common mechanism — the independence property, named fifty years early
- OWASP. “Cross Site Request Forgery.” OWASP Community. — why an attacker who cannot read the response still profits
- OWASP. “Cross Site Scripting.” OWASP Community. — the reflected/stored definitions, and the stored-vs-reflected × server-vs-client matrix
- HTTP Archive. “Security.” Web Almanac, 2025. — measured adoption for every header in this series, and the unsafe-inline share among deployed policies
- W3C. “Content Security Policy Level 3.” W3C Working Draft, 2026. — a decade-old Working Draft deployed on a fifth of the web
- NIST. “Defense in depth.” NIST Computer Security Resource Center Glossary. — the layered-or-stepwise framing this piece argues is insufficient on its own
- Google. “A safer default for navigation: HTTPS.” Google Blog, 2025. — the Chrome 147 and Chrome 154 milestones for HTTPS-by-default
- MDN. “Web security.” MDN. — the attacks/defenses/threat-modeling split, and the best available map outside this series
- MDN. “Integrity-Policy.” MDN. — the header syntax, enforcement model, and browser-availability status
- WebKit. “WebKit features in Safari 26.0.” WebKit, 2025. — Integrity Policy enforcement for script destinations in Safari 26.0
How to cite
Mangalapilly, Y. J. (2026, August). Code From Strangers. Saṃhitā Notes. https://yesudeep.com/blog/code-from-strangers/ @online{mangalapilly2026code,
author = {Yesudeep Jose Mangalapilly},
title = {Code From Strangers},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {August},
url = {https://yesudeep.com/blog/code-from-strangers/},
urldate = {2026-08-12},
} Yesudeep Jose Mangalapilly. “Code From Strangers.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/code-from-strangers/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - Code From Strangers
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/code-from-strangers/
Y2 - 2026-08-12
ER - Webmentions
Annotations
Thank you — your note is held for review and will appear once approved.
Thank you — your note is published.
Please sign in below to leave a note.
