<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Jsmillions · XB Field Notes</title><link>https://www.xbcatm.com/tags/jsmillions/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/jsmillions/index.xml" rel="self" type="application/rss+xml"/><item><title>The zero-JavaScript static site is a defensible choice</title><link>https://www.xbcatm.com/essays/zero-js-as-a-defensible-choice/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/essays/zero-js-as-a-defensible-choice/</guid><description>Why turning off scripting on a content site is a security and reliability decision you can justify with headers, not taste.</description><content:encoded>&lt;p&gt;A static site does not need JavaScript to be defensible; the absence of a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag can itself be the point. This essay argues the engineering case for shipping a site whose script lanes are closed by policy, then proves the claim with the header this publication actually serves.&lt;/p&gt;
&lt;p&gt;The argument has three parts: the threat surface you delete, the contract that makes deletion observable, and the cases where deleting the layer is the wrong call.&lt;/p&gt;
&lt;h2 id="the-threat-surface-you-delete-by-not-having-a-script-lane"&gt;The threat surface you delete by not having a script lane&lt;/h2&gt;
&lt;p&gt;A page that executes no own or third-party script removes an entire class of failure and abuse at once. Cross-site scripting lives in a document that can run injected code; a document that cannot run code at all has nothing for an injection to escalate into. Third-party trackers and widgets are gone because there is no script tag to embed them. Supply-chain risk from accidental dependency drift in the browser bundle is gone because there is no bundle. Each of these is a bounded, nameable surface — the small-systems property of choosing boundaries you can articulate.&lt;/p&gt;
&lt;p&gt;None of this claims the site is &amp;ldquo;secure&amp;rdquo;. It claims the attack surface is narrower and, crucially, easier to reason about because it is enumerated by construction rather than by audit.&lt;/p&gt;
&lt;h2 id="the-header-that-makes-the-boundary-enforceable"&gt;The header that makes the boundary enforceable&lt;/h2&gt;
&lt;p&gt;A taste is not a policy. The difference here is that the no-script contract is enforced by the server, not by developer convention. The live response of this site carries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;script-src 'none'&lt;/code&gt; directive is the enforceable part. Even if a future release lets a stray &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; into a page, the browser refuses to execute it. The absence of JavaScript is therefore a served contract, checked by the verifier on every build (&lt;code&gt;script-free&lt;/code&gt;), not a hope. This is the difference between saying &amp;ldquo;we do not use JS&amp;rdquo; and &amp;ldquo;the public edge refuses to run JS&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="what-the-static-structure-buys-after-the-removal"&gt;What the static structure buys after the removal&lt;/h2&gt;
&lt;p&gt;With no scripting, the content model reverts to something closer to published documents. Caching is simpler because a static file has a stable byte identity and an ETag rather than a mutable DOM. Reading the served HTML in a terminal, using &lt;code&gt;view-source&lt;/code&gt;, or indexing the tree is straightforward because the artifact is ordinary files on disk. The trade the site accepts is deliberate: no interactive state, no client-side rendering, no feedback that needs a view model. For a documentation and operations site that is the correct exchange, and it is worth writing down.&lt;/p&gt;
&lt;h2 id="when-the-zero-js-claim-is-the-wrong-call"&gt;When the zero-JS claim is the wrong call&lt;/h2&gt;
&lt;p&gt;The boundary is a feature, not a badge of purity. A site that genuinely needs client-side interactivity — a realtime dashboard, a document editor, a payment flow — should not contort itself into serving static HTML and then bolting on a workaround. The wrong application of this decision is to shave a product to fit the constraint. The right application is to recognize that content that is read, searched, and archived profits from being a document, and to reserve scripting for where a document is insufficient.&lt;/p&gt;
&lt;p&gt;For this property, the served header is the evidence, and it is reproducible on any release by reading the public edge. That is the difference between a preference and a deployed decision.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src"&gt;MDN: Content Security Policy (&lt;code&gt;script-src&lt;/code&gt;)&lt;/a&gt;, directives that disable script execution, checked 2026-08-13.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://portswigger.net/web-security/cross-site-scripting"&gt;PortSwigger: What is cross-site scripting (XSS)?&lt;/a&gt;, the attack class removed when scripts cannot run, checked 2026-08-13.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-community/controls/Content_Security_Policy"&gt;OWASP: Content Security Policy&lt;/a&gt;, CSP as a defense-in-depth control, checked 2026-08-13.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>