Client-Side Rendering (CSR)
In 2006, an invention sparked a change in how the internet works: the Infinite Scroll. Aza Raskin's design removed the page boundary; as the user reached the bottom of what was loaded, more content appeared, fetched on demand and rendered in place. The pattern spread quickly. Twitter, Facebook, Pinterest, and most of the consumer web adopted variations within a few years. To deliver that experience, applications had to become dynamic and responsive to what each user was doing in real time. This led to the creation of client-side rendering platforms and, fundamentally, changed the way internet-enabled technology is built. It also created a shift in the internet supply chain. The most consequential effect of that shift sits at the layer where the user actually meets the system.
The Client Runtime is the control layer on the client device. Server logic, network paths, and back-end services all matter to the system's correctness; the user only ever experiences the system as it appears in the Client Runtime. It is also the least protected layer in the modern technology stack.
Attack the Glass (ATG), in its most direct framing, is an attack on the Client Runtime. The Client Runtime draws the Glass the user interacts with in real time, and it sits in the one position every other layer of the stack assumes someone else has already secured. That vulnerability is structural: it comes from the technique that produces the modern Client Runtime, Client-Side Rendering.
Client-Side Rendering (CSR) is the technique by which a Client Runtime assembles and modifies its execution at runtime rather than at build time. Older client environments could only present a user experience compiled from fixed source. That limitation was removed decades ago from modern programming languages and runtimes. Two users running the same application on the same device against the same originating source can have different runtime experiences depending on who they are, where they are, what they are doing, and other contextual conditions. Modern applications are not uniform and procedural. They are reactive and dynamic to the individual user.
JavaScript executing in a web browser is the most common and visible expression of CSR, and the web carries by far the largest CSR-capable installed base. CSR is not specific to the browser; the technique drives mobile, desktop-shell, immersive, embedded, and productivity runtimes across the deployed landscape. Wherever a runtime is structured to load or interpret content at runtime that influences what is rendered or what executes, CSR is the technique in use, and the Client Runtime is exposed to ATG.
Not every Client Runtime is exposed. Precompiled-only Client Runtimes (some embedded firmware, certain real-time control loops, certain safety-critical avionics) are out of scope. These Client Runtimes are immune to ATG-style attacks because the language and platform do not support dynamically loading alternative code or modifying user interfaces at runtime.
Two pathways carry the runtime modification that creates that exposure. The first is direct: the runtime loads executable code over the network and incorporates it into the running application. The second is indirect: content interpreted at runtime generates further executable behavior the runtime then runs. Both produce runtime code execution, but only the first pathway looks like "loading code" to a defender.
CSR-capable Client Runtimes are unprotected, unmonitored, and exposed. Thousands of attack vectors target millions of sites, applications, and source repositories that feed those runtimes. The attacks infiltrate Client Runtimes across a wide range of devices and platforms, leave nearly no forensic signature or attribution behind, and meet no runtime behavioral monitoring at the rendering surface. Checking a delivered file against a known hash remains valuable, but that check stops at the moment of delivery and never reaches the running application. The result is a structural blind spot in the integrity of modern computing devices: an open path to modify the information rendered on the screen, the interface the user interacts with, and the decisions the user makes by trusting what they see on the Glass.
Within that exposure, an attack-mode hierarchy holds. The most likely and by far the most dangerous category of ATG attack is the modification of information itself: the data, content, media, and features the user sees. Functional disruption (application failure, alert suppression, behavioral subversion of features) sits above information modification in technical sophistication and below it in frequency, payoff, and damage. The D5 effects framework formalizes this hierarchy.
CSR is not a defect to be patched out. It is the architectural pattern that produced the shift from server-rendered to client-rendered computing, the shift that enabled infinite scroll, reactive interfaces, data-driven user experiences, and the modern application stack. There is no return to server-only rendering at scale. The defensive posture must be purpose-built for the CSR-capable Client Runtime.
1.1: The Client Runtime
The Client Runtime is responsible for assembling, executing, and rendering the user's experience of the system. Everything the user encounters at the device passes through it: what is shown on the screen, which controls work and what they do, what background processes run, what information is collected, and whether alerts and warnings surface or stay silent. Every other layer reaches the user through it. The user only ever experiences the system as the Client Runtime presents it.
Client Runtimes fall into two structurally distinct categories.
The first is the CSR-capable Client Runtime: a runtime that can load, interpret, or generate content at runtime in ways that influence what is rendered or what executes. Modern web browsers, mobile-application runtimes, hybrid desktop shells (Electron, Tauri), AR and VR runtimes, smart-TV applications, vehicle infotainment systems, IoT and operational technology dashboards, and productivity-tool extension hosts all fall into this category. The CSR-capable Client Runtime is where ATG lands.
The second is the precompiled-only Client Runtime: a runtime that loads and executes only precompiled code, with no mechanism for loading or interpreting runtime scripts and no mechanism for content-driven runtime modification. Some embedded firmware, certain real-time control loops, certain safety-critical avionics, and a small set of hardened or minimal runtimes fall into this category. Precompiled-only Client Runtimes are not exposed to ATG. They cannot load runtime scripts, and they cannot modify behavior in response to fetched content.
The distinction is operational, not theoretical. Whether a given platform is in scope depends on what its Client Runtime actually does: whether it loads runtime scripts, accepts runtime configuration, evaluates content as code, or interprets data in ways that alter behavior. A Client Runtime that is technically capable of CSR but configured to load no runtime resources is a low-exposure case. A Client Runtime that loads dozens of runtime resources from many providers is a high-exposure case. The same platform can sit anywhere along that spectrum depending on configuration and use. Exposure is a property of what the Client Runtime actually loads, not of the platform category it belongs to.
Inside the Client Runtime, the Client Runtime Boundary is the moment a fetched file stops being something a defender can open and inspect and starts being code that is running. The supply-chain controls that defenders rely on operate before that boundary. After the boundary, that code holds whatever permissions the host application holds, it can reach for further code of its own, and the conditions ATG needs are all in place. The boundary is the same regardless of platform.
1.2: Client-Side Rendering
Client-Side Rendering is the technique by which final assembly and execution of an application happen on the client device, inside the Client Runtime, rather than on the server. The application as it executes is composed at runtime from sources that may include the base application, scripts and modules fetched at runtime, configuration retrieved at startup, content and data loaded continuously during the session, model weights and prompts in AI-augmented runtimes, and any other resource the runtime is structured to consume. Three primitives are central: the runtime, the rendering surface, and the assembled application.
CSR is a design approach, not an exclusive technique of any single engine. Across all platforms that implement it, user experience and executable behavior are constructed at runtime, not fixed at build time. Mobile-application rendering frameworks, hybrid desktop shells, AR and VR scene graphs, smart-TV runtimes, vehicle infotainment, IoT and OT dashboards, productivity extensions, plugin hosts, and notebook front-ends are all CSR runtimes. JavaScript in the browser is the most common expression because the web carries by far the largest installed base; the browser is not the limit of CSR.
Two pathways carry the runtime modification that distinguishes CSR from server-rendered or precompiled execution.
The first pathway is direct: the runtime loads source code, modules, scripts, or other executable files from the network at runtime and incorporates them into the executing application. This is the pathway most commonly understood as CSR.
The second pathway is indirect: the runtime loads content (data, media, configuration, templates, prompts) that code already present in the runtime then interprets. The interpretation generates, compiles, or otherwise produces further executable behavior, which the runtime then runs. JSON evaluated as code, configuration that declares scripts to load, templates that carry executable expressions, data-driven dispatch tables, and model outputs that drive control flow are all instances of the second pathway. Both pathways produce runtime code execution. The second is the one that defeats the usual line between content and code: inside a CSR runtime, content the application interprets can become code the application runs. Data as Code and Code That Writes Code are the two ways an attacker turns that second pathway into an attack.
1.3: The Move from Server to Client
The web migrated first. Around 2010, single-page-application frameworks reached mainstream adoption: Backbone, AngularJS, then React, Vue, Svelte, and the modern framework ecosystem. jQuery had set the stage through the second half of the 2000s as the dominant DOM-manipulation library but was not itself an SPA framework. The same migration carried into mobile through cross-platform frameworks (React Native, Flutter, Xamarin) and hybrid WebView frameworks (Cordova and Capacitor), into the desktop through Electron and Tauri, into immersive computing through AR and VR runtimes, into appliances through smart-TV and signage runtimes, and into productivity through notebook front-ends and extension ecosystems.
Hardware carried the new load. RAM and CPU growth on consumer devices made it practical to render rich, animated interfaces in the runtime without sacrificing user experience. JavaScript engine performance (V8), the rise of Node.js and npm, and the broader maturation of the front-end toolchain compounded the effect. Each generation of capability (richer animations, real-time visualizations, more complex interactivity) drove broader adoption of CSR frameworks. The same pattern pulled more languages into the ecosystem to deliver the experience features the runtime had to provide.
1.4: Why CSR Won
Richer user interfaces and more adaptive user experiences are the obvious technical advantages. CSR provided more dynamic interaction, perceived performance and responsiveness, and the app-like behavior the web had previously lacked. It also simplified operations: deploying static assets to a CDN and letting the client assemble the application removed much of the backend complexity from each release. And it gave development teams single-page-application frameworks, which provided an architecture, tooling, and patterns for building modern web applications. Each of these was a real, decisive improvement. But the transition to CSR was not just a technical advantage; it was also a financial one.
CSR's financial advantage was structural: it moved the cost of rendering and computation off vendor servers and onto the consumer device. The shift drove parallel demand for high-speed home internet, mobile bandwidth, expanded device RAM, and expanded local storage. Every layer of the technology supply chain benefited: application owners operated leaner backends, network operators sold more bandwidth, hardware vendors sold more capable components, and platform companies sold more advanced finished devices at higher margins. The same shift built the high-end consumer hardware market (the flagship smartphone is the most visible example) and the platform-company valuations that depend on it.
Consumers paid for all of it. The cost was spread across billions of users and billions of devices, so it was rarely noticed as a transfer. The Compute Tax is that transfer: a tax rather than a bill, an extraction with no line item and no discount, where the user pays more in device, bandwidth, and power and receives the same product at the same price. The provider ships a blueprint rather than a finished product, and the user's device pays to build it. Split across billions, no one sees the cost, or the fortune it hides. That one decision is both what saves the provider money and what leaves the runtime open to attack, which is why the design is unlikely to be given up. CSR won because the cost could be sent to someone who would not see it itemized.
1.5: The Application Blueprint
What a CSR application ships is a blueprint, not a finished product: a plan the device realizes just in time at render. The base application is a thin shell that fetches, assembles, and executes the rest of the application from remote sources at runtime (a bootloader, in engineering terms). The running application is composed from dozens or hundreds of independent sources: scripts, modules, fonts, configuration, feature flags, models, ad units, telemetry. The Just-in-Time App Blueprint is that pattern: the device builds the application at the moment of render, from parts fetched live from sources the owner does not control. A blueprint is supposed to guarantee the same build every time. This one guarantees the opposite: the application as it executes is different from the application as it was built.
The blueprint is also a trust list, and that part has no counterpart in shipped software. Every remote source the plan names is a party the device will trust at render. The author of that plan decides who those parties are, and the device carries the decision out. The person operating the device is not shown the list, cannot inspect what any entry on it returns, and cannot decline one without losing the application. Conscripted Trust names the case where one party grants the trust and a different party carries its consequences.
The client is not powerless, and the distinction matters. Extensions, script blockers, enterprise policy, and network-level filtering can each refuse a source outright, and refusing one that the application depends on usually breaks the application. What none of them can do is accept a source on conditions, or establish that a permitted source returned what it was expected to return. The client can refuse the relationship. It cannot condition it.
Two boundaries keep the claim accurate. Installed software also carries dependency decisions its user did not make, but that dependency set is fixed at install and does not change between launches, so it can in principle be examined; a blueprint's trust set is resolved per render from addresses that may return anything, so there is no build of the application whose trust set could be inspected at all. And server-side rendering delegates in the same way, but composes inside the operator's own boundary, where the operator can observe the result and bears its consequences directly. Delegation itself is not new. What is new is that the delegated relationship now resolves in a place neither the operator nor the user can observe.
Commercial logic reinforced the blueprint pattern. Keeping proprietary code, algorithms, and behavior in the runtime layer (rather than in shipped binaries) raises the cost of theft, reverse engineering, and behavioral monitoring. The vendor gains IP protection on top of the deployment-flexibility benefit. The same pattern enabled the rise of analytic and telemetry frameworks that capture user interactions in real time. Those frameworks became the foundation for engagement-driven design (infinite scroll and its descendants) and the broader behavioral-monitoring ecosystem built on top of CSR runtimes.
1.6: The Perpetual State Machine
Assembly at render implies a moment when assembly finishes: the parts arrive, the application composes, and from then on one built thing is running. Sufficiently interactive applications do not settle that way. The composed application continues to change for as long as the session lasts, and it changes in two ways that have nothing to do with each other.
A user drives the first kind of change. State and workflow are a combinatorial function of the sequence of interactions, the date and time, the platform, the location, the device, and whatever preferences and entitlements the user carries. Two people using the same application on the same day arrive at different rendered states by different paths, and neither path was enumerable in advance. This much is familiar. It is the ordinary behavior of any stateful interface and it is not, on its own, a security property.
The parts drive the second kind, and this one belongs to CSR specifically. The set of parts the runtime assembles is not fixed between renders. What varies from one render to the next is therefore not only the application's state but its function: the same plan, realized from a different collection of parts, is a different program. The owner ships one plan. The device can build it differently on every pass.
The Perpetual State Machine is the application that results. A finite state machine is bounded, enumerable, and testable: its states can be listed and a test can walk them. Combinatorial input alone does not break that, and it never did; server-rendered applications and native binaries carry long input histories too, and the methods for reasoning about them are mature. What breaks it is the second kind of change. Where the parts are not fixed between renders, the transition function is not fixed either, so there is no stable machine to enumerate and no fixed target for a test to certify. There is not one consistent application. Each render on each machine is a unique instance, potentially never to recur in that exact form again.
The claim needs a boundary. A static page that renders once and does nothing further is not a Perpetual State Machine, and neither is a form-and-submit application whose parts are pinned at build time. The description holds where an application is sufficiently interactive and assembles its parts live, which is the prevalent shape of modern client applications rather than a property of all of them. Where it holds, the consequence reaches past testing. A defender comparing a suspect render against a known-good one has no known-good one to reach for, because no two renders were ever required to match.
1.7: Code and Data on Different Paths
CSR architectures separate the delivery of code from the delivery of data. Application logic, frameworks, and scripts travel one path. Content, configuration, and responses travel another. The two are sourced from different providers, arrive at the runtime under different trust assumptions, and meet only inside the runtime where the application finally executes. They were never written or reviewed together as one piece of software, but both are required for the application to run.
Upstream defenses can be strong. Code is signed and verified at build time; CDN delivery is authenticated; data endpoints use TLS and access control. The final assembly happens inside an untrusted container, where none of those defenses operate.
Every part passes inspection. Nothing inspects the assembly.
1.8: Verification Ends at Delivery
All meaningful verification happens before delivery. Code review, signing, scanning, hashing, dependency analysis, and supply-chain attestation (the signed record of how a component was built and by whom) all operate on files at rest, before delivery to the Client Runtime. Once execution begins inside the Client Runtime, no further verification occurs. The trust granted at delivery is the only trust the runtime ever receives.
Mobile WebViews and JavaScript bridges inherit the gap. So do vehicle infotainment runtimes running embedded browser engines. So do the operator screens of industrial control systems when those screens are built with CSR frameworks, and so do smart-TV applications and productivity-tool extension hosts. The structural property follows from the architecture, not the platform: any runtime that grants trust at delivery and does not continuously re-verify thereafter has the same exposure.
NIST SP 800-207 is guidance, not a standard, and it sets out tenets for Zero Trust Architecture rather than a conformance checklist. The Client Runtime works against those tenets structurally:
- Continuous verification: absent after the initial load. Trust is granted once at session start, and the runtime offers no mechanism to re-verify sources, code, or data thereafter.
- Least privilege at runtime: loaded resources operate at the host application's permission level. No scoping mechanism restricts a script or data-driven code path to less authority than the page or app it ran inside.
- Explicit denial of implicit trust extension: loaded resources implicitly inherit the trust of the runtime that loaded them. The act of loading is the act of trusting.
- Continuous re-evaluation under changing context: the runtime makes the trust decision once. Subsequent context changes (new user, different network, elevated privilege, anomalous behavior) do not trigger re-evaluation. Every Client Runtime inherits the same arrangement: verification stops at delivery, and the trust granted at load is the only trust the runtime ever applies. That trust is usually not the product of verification at all. In many client-side stacks a library, service, or technology is included because it is popular, well-branded, and widely adopted, not because anyone verified its behavior or intent. Herd Trust is inclusion by reputation: the decision to include rests on ubiquity, and no verification of behavior or intent follows. Widespread use feels like herd immunity, but in reality is Herd Trust: adoption does not provide any true safety and the sense of trust is from being a part of a larger group. Trusted by the herd and never verified, the resource is then granted the runtime's full rights at execution. The runtime runs on faith.
Herd Trust explains why an application author selects a source. Conscripted Trust explains why that selection binds someone else. The two compose: a resource is included because the crowd already uses it, and the person in front of the Glass inherits that reasoning without being consulted, without visibility into what the resource returns, and without any way to accept it on narrower terms.
1.9: The Allowlist Fallacy
CSR runtimes do not require remote sources to be trusted. A runtime could pin every dependency, refuse to load anything not declared at build time, and reject any data that contained executable instructions. Almost none do. The prevailing implementation pattern is broad runtime trust: scripts can fetch and execute further scripts; fonts and assets are loaded from arbitrary CDNs; configuration endpoints contacted at startup are trusted to influence behavior. Strict-pinning runtimes exist in narrow cases (some embedded contexts, some hardened browser modes); they are exceptions.
The Allowlist Fallacy is the mistaken belief this pattern rests on: that adding a source to an allowlist (a domain, CDN, package, or endpoint approved as trusted) also approves everything that source will later return. An allowlist authorizes an address, not the content it ships, and that address can, by design, return different or mutated content on any later load. An allowlist approves the sender, not the package. The pattern is an implementation choice, not a structural property of CSR, and it is so widespread that the consequences are effectively general. The Manifest Illusion, Data as Code, and Code That Writes Code all depend on it.
1.10: The Manifest Illusion
Runtime loading is transitive. Once execution begins, loaded resources pull in further resources with no obligation to surface what they reach for. A script inserts another script via dynamic
The mechanism is not browser-specific. A mobile WebView reaches the same way through a JavaScript bridge. A hybrid desktop shell such as Electron does the same wherever its web layer is wired through to Node.js. A vehicle infotainment runtime loads configuration delivered over the air, pulled straight from a server after the vehicle is in service and without a new firmware image, and that configuration fetches further modules. An IoT dashboard loads its UI extensions from a remote registry.
The Manifest Illusion is the structural fact that no complete and accurate list of what actually runs can exist where an application loads code dynamically, generates code at runtime, or fetches parts live. The manifest defenders hold (a build-time package list, a lockfile, an SBOM, or the set of permitted origins in a runtime-enforced CSP allowlist) records what was declared, not the code that actually executes, and the two drift apart with nothing to reconcile them.
Examining the root sources of an application does not reveal the eventual runtime trust graph; the trust chain forms at runtime, and no file a reviewer can open beforehand shows it. You have a manifest from the plans, not a true manifest of what was built and runs.
1.11: Data as Code
CSR runtimes blur the data/code boundary. Configuration declares scripts to load. Templates carry expressions that execute. JSON is evaluated as code. JSONP returns code wrapped as a data response. Dynamic import() consumes module specifiers fed from configuration. Feature-flag services ship code paths as feature toggles. Model outputs drive control flow wherever a model is allowed to act on its own. A response that looks like data is, in many runtimes, a vector for source injection.
The data path becomes a code path without crossing any visible boundary. That crossing is Data as Code. The same property holds across the Client Runtime landscape: a vehicle infotainment system's remote configuration channel, a mobile application's feature-flag service, an IoT dashboard's content endpoint, and a productivity tool's plugin manifest each carry the same permeability. The Two-Stage Attack is the operational form of the same permeability, used deliberately by an attacker.
1.12: Code That Writes Code
CSR runtimes routinely allow code to generate code at runtime, compile or interpret it on the fly, execute it, and discard it. eval and the Function constructor produce executable code from a string. new Function(...) builds a function body at runtime from arbitrary input. Dynamic import() resolves module specifiers computed at runtime. WebAssembly compiles from buffers fetched over the network. Runtime template engines turn data into executable functions. Embedded Just-in-Time compilers execute domain-specific code supplied at runtime.
The generated code never appears in any source file, any build output, or any stored asset. It exists only in the moment of execution and leaves no audit surface. A forensic investigator inspecting the running runtime might see it; one inspecting the source code or the stored files will not. Code That Writes Code operates the same way in mobile runtimes, desktop shells, embedded JavaScript engines, AR and VR scene-graph engines, and productivity-tool macro engines. Malice Without Malware rests in part on this: the attack leaves nothing behind for an audit to find.
1.13: The Glass Beyond the Browser
CSR runs wherever there is a screen and a CSR-capable Client Runtime. The browser is the largest instance, not the only one. Mobile WebViews and hybrid app shells, Electron and Tauri on the desktop, smart-TV and signage runtimes, vehicle infotainment, AR and VR scene engines, IoT and OT dashboards, notebook front-ends, productivity-tool extension hosts, and plugin marketplaces are each Client Runtimes in their own right.
This breadth is the Glass Beyond the Browser: the glass is any surface where a runtime renders for a human*.* The structural properties and exploitation mechanisms of CSR operate the same way in every one of these runtimes. The consequences differ by platform; the architecture does not. A defensive vocabulary built for a single environment cannot serve all of them.
1.14: Malice Without Malware
The malware-centric framing has shaped the security industry for three decades, and it has earned its place. Static binary defenses remain a real and necessary line. The categories most relevant to CSR sit outside that framing: manipulation, deception, behavioral steering, supply-chain injection that leaves no altered binary, and runtime trust abuse. None of these is well captured by the term "malware," and the assumption that makes anti-virus work, that there is a fixed file sitting on disk to scan, does not survive a runtime where the loader is opaque, data is code, and code generates code.
A defensive vocabulary that lags the actual threat is itself part of the problem. Anti-virus, signature scanning, and hash-based detection do not detect runtime manipulation of the rendered application, runtime injection of third-party trust, or content-driven generation of executable behavior. What is required is a defensive posture purpose-built for client-side surfaces: client-side attack surface management, runtime behavioral monitoring at the rendering surface, and a new generation of anti-manipulation and anti-deception capability.
Malice Without Malware names that gap. Malicious code, from a malicious source, is what defenses hunt. ATG is neither. It can produce a malicious outcome with clean, legitimate code from a trusted source, so the tools built to catch bad code from a bad source never fire. The malice is real; the malware is absent. CSR is the foundation of the modern internet, the smartphone economy, the desktop application ecosystem, the AR and VR runtime, the in-vehicle infotainment system, and a growing share of industrial control interfaces. There is no patch for CSR. There is no "out." CSR is the environment in which the modern technology stack lives, and a defensive vocabulary built for a different environment cannot remain the operational center of the security response.
1.15: CSR and Client Runtime AI
Client Runtime AI is moving from research curiosity to default capability, and its arrival on consumer devices compounds every property of CSR. The model files loaded into the runtime are fetched at runtime like any other CSR-loaded resource. LoRA adapters (small add-on files that change how a model behaves), prompts, system instructions, and tool definitions delivered to the runtime all become second-stage parts that can be combined subject to the same Manifest Illusion and Data as Code mechanics.
Where a model is allowed to act on its own inside the Client Runtime, it can take actions on the user's behalf with permissions far beyond what a traditional script can claim. Model-driven personalization makes the rendered application functionally unique per user, which makes the manipulation surface harder to detect, audit, or compare across populations.
Client Runtime AI does not introduce a new vulnerability; it amplifies the one already present. CSR is the architecture, and ATG is the vulnerability CSR produces.