Every security standard added in the last 20 years was designed to protect the publisher from the user — and the user from the publisher. Nobody asked whether the user should have any power at all.
The early web had no security model because it had no threat model. Pages were static, servers were simple, and "the web" meant hyperlinked documents — not applications. What followed was a reactive sequence: each exploit forced a new lock, each lock reduced flexibility, and none of them were designed with user agency in mind.
Any page could load any resource from any server and read its contents. Cookies were introduced but sent cross-site — nobody thought to block them. The web was genuinely permissionless at the application layer. Flash accessed camera and microphone silently, with no browser prompt at all.
XMLHttpRequest shipped with no cross-origin block — pages freely fetched third-party APIs and read full responses. ActiveX had full system access in IE. Browser plugins ran with OS-level privileges. Powerful, dangerous, and entirely publisher-controlled.
Cross-site request forgery attacks forced browsers to block cross-origin response reading. The Same-Origin Policy hardened. Geolocation arrived in Firefox 3.5 with an inconsistent notification bar — easily dismissed, no persistent state, no way to query what had been granted.
WebRTC introduced camera/mic prompts — per-call, per-session, no memory. Notifications had its own grant model. Geolocation had another. Each spec team built their own UI, persistence, and revocation. No unified way to query what a site had been granted.
navigator.permissions.query() finally lets pages ask "what have I been granted?" without triggering a prompt. A meaningful improvement. But the model didn't move: the site requests, the browser intermediates, the user reacts. The user cannot proactively grant, scope, or delegate a permission.
"Flash, ironically, was closer to a user-first model in one narrow sense — plugins were user-installed, user-trusted software. Browser vendors killed that model for good security reasons. But they never replaced the user agency part of it."
A request from your application to the open web travels through six distinct enforcement layers. Each was added reactively after a specific attack. None were designed with end-user agency in mind. Click any layer to expand it — then click a mechanism for its full story.
integrity attribute on <script> and <link> tags declares a SHA hash. If the fetched resource doesn't match, the browser refuses to execute it — blocking supply-chain attacks via CDN compromise.require-corp, prevents a page from loading any cross-origin resource unless that resource explicitly opts in with a CORP header or CORS response. Required to enable SharedArrayBuffer and high-resolution timers.window.opener — the popup talks back to the parent. COOP breaks this entirely. Teams building login or checkout flows must redesign around postMessage-based protocols or full redirect flows, adding weeks per integration.SameSite attribute controls when cookies are sent on cross-site requests. Strict blocks cookies on all cross-site requests including top-level navigations. Chrome made Lax the default in 2020.Strict blocks cookies even when a user clicks a link from email to your site — they arrive logged out. Lax breaks embedded forms, POST requests from external sites, and multi-domain architectures that assumed cookies flowed freely across navigations.Access-Control-Allow-Origin. The browser always sends the request and the server always processes it — but withholds the response from JavaScript if the header is absent or mismatched.DELETE from a malicious origin executes on the server — CORS only stops JS from reading the response. This surprises teams who treat CORS as a security boundary. It is a readability boundary. Server-side auth is still the only real protection.'unsafe-inline' — which disables XSS protection entirely. The overhead of maintaining an accurate allowlist across every third-party dependency, CDN version bump, and A/B test framework makes strict CSP operationally prohibitive for most teams.<iframe> on a different origin. DENY blocks all framing. SAMEORIGIN permits only same-origin frames. ALLOW-FROM was deprecated and ignored by most browsers.ALLOW-FROM means publishers wanting to permit embedding on specific partners must use CSP frame-ancestors — a newer directive still not universally supported. Every Stripe checkout or support widget embed requires bilateral coordination.Referer header when navigating or loading subresources. Options range from no-referrer to unsafe-url. Browsers changed their default to strict-origin-when-cross-origin in 2021.max-age — up to 2 years. Subsequent HTTP requests are upgraded to HTTPS before leaving the browser, preventing SSL stripping attacks.http:// references in content, embeds, and legacy assets — including third-party widgets that haven't migrated. Pages break silently with no useful error message to end users.Notice that zero of these layers give the user any affirmative controls. Each is either a server-side declaration (publisher decides) or browser-side enforcement (vendor decides). The user is entirely reactive.
CSP's premise is elegant: tell the browser which origins are trusted for scripts, styles, images, and connections — block everything else. The implementation is where friction compounds. Every third-party dependency, CDN, analytics tool, font provider, or A/B test framework requires a server-side header change and a redeploy.
"Most real-world CSP headers in production contain 'unsafe-inline' — which disables XSS protection entirely. The allowlist remains, adding developer friction, while providing no security."
Toggle sources on and off. Pay attention to what happens when you enable 'unsafe-inline'.
Modern CSP offers nonce-{random} as an alternative to 'unsafe-inline' — each inline script gets a unique token generated server-side per request. This works, but now every HTML response needs a fresh nonce, CDN caching is invalidated, and third-party snippets that inject their own scripts break anyway because they don't receive the nonce.
Zoom out across all 16 mechanisms and a single pattern emerges. Every one was added in response to a specific attack — XSS, CSRF, Spectre, clickjacking, DNS poisoning. None were designed by asking "what should the user be able to do?" They are publisher protections and vendor constraints dressed up as user safety.
"The web's security model was never designed. It accumulated — one lock at a time — until navigating it required an enterprise team just to ship a button."
The security mechanisms documented here aren't going away — nor should most of them. The problem isn't that the web has security. The problem is that security has been the only frame. No equivalent investment has been made in user capability, user memory, or user agency. That's the gap.
Instead of each publisher storing a user record, the user holds a portable identity that publishers can request access to — not the other way around.
The web has no memory. The user's layer does — a semantic graph of what they've read, decided, and built, accessible to any tool they trust.
Instead of reacting to publisher prompts, users set their own policies: "allow api.acme.com camera access for 30 minutes, proxied through my layer."
When a user's data or attention generates value, the ledger is visible to them — not hidden inside an ad server. They choose to participate or opt out.