<?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>Zero-JavaScript sites (legacy: jsmillions) · XB Field Notes</title><link>https://hk.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://hk.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://hk.xbcatm.com/essays/zero-js-as-a-defensible-choice/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.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 explains how to verify the policy at the public edge. The header below is the repository’s intended configuration, not a fresh observation of production.&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;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/essay-nojs.detail.en.svg" alt="An enforcing no-script browser boundary allows document assets and native interaction but blocks page scripts, inline handlers and JavaScript URLs. Content, publishing, generator and server responsibilities remain. Three separate required checks inspect generated artifacts, delivered CSP and browser behavior." width="720" height="1690"&gt;&lt;figcaption&gt;Assumes the article’s complete enforcing CSP with no more-specific script allowance. Report-Only does not block. The three checks shown are required evidence, not completed tests.&lt;/figcaption&gt;&lt;/figure&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 the client-side script dependencies that would otherwise need patching and review. An effective no-script CSP blocks injected script execution in conforming browsers, but HTML injection can still change text, links, and page structure. Trackers can also use images, navigation, or server logs without JavaScript. This site separately avoids third-party browser dependencies; that is a broader requirement than removing script tags. Browser-bundle dependency drift disappears when there is no bundle, while build-tool and server dependencies remain. 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 server delivers a policy that the browser enforces. The example Caddy configuration in this repository specifies:&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. With this complete policy and no more specific script allowance, a conforming browser refuses page scripts, inline handlers, and JavaScript URLs. A &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; header would only report violations, not block them. Later additions of &lt;code&gt;script-src-elem&lt;/code&gt; or &lt;code&gt;script-src-attr&lt;/code&gt; need review because they can override the fallback for their respective script categories.&lt;/p&gt;
&lt;p&gt;The build verifier checks generated artifacts for executable scripts, handlers, and URLs. It cannot prove that a reverse proxy serves the intended CSP. Fetch representative HTML, error pages, and redirect destinations separately; inspect the actual enforcing header and test browser behavior. Source inspection, response inspection, and browser execution are three distinct checks.&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 operates on HTTP representations, not on a page’s later DOM mutations. Static output makes those representations easy to inspect and version; an ETag and freshness policy still depend on server configuration. 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 script-managed state, no client-side rendering, and no feedback that needs a JavaScript view model. Native links, keyboard navigation, CSS behavior, and elements such as &lt;code&gt;details&lt;/code&gt; can still provide interaction. 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 useful evidence for the response sampled. It does not prove universal browser support or that every route has the same policy. Keep the underlying content safe to render, protect publishing access, and maintain the generator and server. The boundary is defensible precisely when its limits are explicit.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.w3.org/TR/CSP3/"&gt;W3C: Content Security Policy Level 3&lt;/a&gt;, policy delivery, script directives, and directive fallback (working draft), checked 2026-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&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-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://portswigger.net/web-security/cross-site-scripting"&gt;PortSwigger: What is cross-site scripting (XSS)?&lt;/a&gt;, script injection and its consequences, checked 2026-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&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-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>