<?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>Essay · XB Field Notes</title><link>https://hk.xbcatm.com/tags/essay/</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/essay/index.xml" rel="self" type="application/rss+xml"/><item><title>Readable evidence before dashboards</title><link>https://hk.xbcatm.com/essays/readable-evidence-before-dashboards/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/essays/readable-evidence-before-dashboards/</guid><description>A release claim needs inspectable observations with time, scope, and provenance; dashboards and individual probes answer different questions.</description><content:encoded>&lt;p&gt;Monitoring dashboards are convenient, but convenience is not the same as evidence. This essay argues that for a small static service, the most auditable form of &amp;ldquo;is the site healthy?&amp;rdquo; is a set of plain-text facts an operator can read, re-run, and paste into a report — not an aggregated panel.&lt;/p&gt;
&lt;p&gt;The distinction matters because the two answer different questions. A dashboard is a lens over many samples, tuned to alert you to trends. A probe is a single, reproducible observation of the public edge. When the job is to prove a release or diagnose a specific request, the probe is the higher-fidelity artifact.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/essay-evidence.detail.en.svg" alt="The scope box limits one matched response to a URL, representation, vantage point and time. Four rows distinguish headers, response-body comparison, TLS checks and a byte manifest. A separate aggregation path preserves raw observations behind trends. Failures and context are retained, secrets redacted, and collection integrity checked." width="720" height="2580"&gt;&lt;figcaption&gt;A response supports a scoped claim. Repeated observations support trends when the underlying records and their provenance remain inspectable.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="the-dashboards-aggregation-is-the-risk"&gt;The dashboard&amp;rsquo;s aggregation is the risk&lt;/h2&gt;
&lt;p&gt;A dashboard turns thousands of raw observations into a series of aggregated panels: average latency, error rate, uptime percentage. Aggregation is where the audit trail thins. An &amp;ldquo;uptime 99.98%&amp;rdquo; figure does not tell you which request failed, which header was wrong, or whether today&amp;rsquo;s release is the one being served. It is a summary that erases the evidence that produced it unless you keep the raw log elsewhere.&lt;/p&gt;
&lt;p&gt;For a confidence property like &amp;ldquo;the edge serves the tree I staged&amp;rdquo;, a dashboard is the wrong tool. A response-body comparison can answer that question for one URL, representation, network vantage point, and instant. A homepage header fetch cannot establish the identity of a whole tree or every CDN node.&lt;/p&gt;
&lt;h2 id="a-probe-you-can-read"&gt;A probe you can read&lt;/h2&gt;
&lt;p&gt;The repository ships a small read-only helper, &lt;code&gt;static/downloads/check-http-headers.sh&lt;/code&gt;, also published at &lt;code&gt;/downloads/check-http-headers.sh&lt;/code&gt;. It prints response headers and the final status/URL. The following is an illustrative excerpt, not a new production measurement:&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;HTTP/2 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html; charset=utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &amp;#39;self&amp;#39;; …; script-src &amp;#39;none&amp;#39;; …
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A real capture can directly show the response status, declared media type, and delivered CSP. The abbreviated example above cannot establish a complete no-script policy: inspect the full enforcing header, including any &lt;code&gt;script-src-elem&lt;/code&gt; or &lt;code&gt;script-src-attr&lt;/code&gt; directives that take precedence for their categories. A Report-Only policy does not block execution. A panel that says &amp;ldquo;edge OK&amp;rdquo; should expose the same underlying capture and acceptance criteria. Both still depend on the probe implementation, TLS verification, routing, and retention of the original output. Reading certificate &lt;code&gt;notAfter&lt;/code&gt; adds an expiry observation; it does not establish hostname matching or trust-chain validity.&lt;/p&gt;
&lt;h2 id="replace-signal-loss-with-a-checkable-statement"&gt;Replace signal loss with a checkable statement&lt;/h2&gt;
&lt;p&gt;The mechanism the site favors instead of a dashboard is a contract enforced during build and a set of repeatable collection steps for the release window. Roughly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offline checks validate the artifact (every internal link, every SVG, the no-script boundary, the checksums).&lt;/li&gt;
&lt;li&gt;Separate public-edge checks record status/headers, response bytes, and TLS identity/expiry. The header helper does not print certificate dates or automatically reject every unexpected HTTP status.&lt;/li&gt;
&lt;li&gt;A byte manifest describes the staged tree; retrieving selected URLs and comparing their bodies establishes sampled delivery parity. A complete claim requires checking all published mappings and representation transformations, not merely hashing the server disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these is a sentence an operator can paste into an issue or a changelog and a future auditor can re-run. None of them requires a panel to be understood.&lt;/p&gt;
&lt;h2 id="keep-enough-context-to-repeat-the-observation"&gt;Keep enough context to repeat the observation&lt;/h2&gt;
&lt;p&gt;Record UTC time, requested and final URL, method, selected encoding, resolved remote IP, client version, exit status, HTTP status, relevant headers, and body digest. Preserve failures as well as successes. Redact credentials, cookies, and personal data before sharing logs. A proxy error page returning 200 can pass a status-only check; comparing a known body or release marker catches a different failure mode.&lt;/p&gt;
&lt;p&gt;A useful release report distinguishes “this response matched at this time” from “the service met its availability objective over a month.” Retain the raw observations behind aggregate metrics, and make a dashboard link back to them where possible. Neither display format creates trustworthy measurements by itself.&lt;/p&gt;
&lt;h2 id="when-you-genuinely-need-a-dashboard"&gt;When you genuinely need a dashboard&lt;/h2&gt;
&lt;p&gt;None of this says observability is pointless. If you operate many instances, aggregate capacity, or long trends, a dashboard is the right view to see the forest. The claim is narrower: when the question is &amp;ldquo;is this one release healthy at the public edge&amp;rdquo;, a reproducible terminal read is a good starting point. Repeated probes, several vantage points, error budgets, and alerting add evidence about intermittent or regional failures that a single successful request cannot supply. A well-designed dashboard can expose those observations without hiding their provenance.&lt;/p&gt;
&lt;p&gt;The decision heuristic is simple. If a claim can be checked with one command, preserve that command’s output together with its scope and acceptance criteria. Use dashboards where aggregation helps, while retaining access to the underlying evidence. That keeps the site&amp;rsquo;s own operational story as auditable as the site&amp;rsquo;s content.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;Curl documentation&lt;/a&gt;, reading response headers from a single invocation, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/logs"&gt;Twelve-Factor: Logs&lt;/a&gt;, treating logs as event streams an operator can read, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/concepts/observability-primer/"&gt;OpenTelemetry: Observability primer&lt;/a&gt;, the complementary roles of logs, metrics, and traces, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/CSP3/"&gt;W3C: Content Security Policy Level 3&lt;/a&gt;, enforcing versus Report-Only delivery and script directive precedence (working draft), checked 2026-10-18.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Freshness and validation in the two-clock cache model</title><link>https://hk.xbcatm.com/essays/caching-as-two-clocks/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/essays/caching-as-two-clocks/</guid><description>Freshness controls reuse without asking; validation checks a stored representation. A small exercise separates the two decisions.</description><content:encoded>&lt;p&gt;Caching has two separate decisions: may a stored response be reused without contacting the server, and how can the client check whether that response still represents the resource? Calling them “two clocks” is a mnemonic. Freshness really is an age calculation; validation is a comparison, not another timer. A response can be fresh by policy while already differing from a newly deployed origin, without the cache violating HTTP.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/essay-two-clocks.detail.en.svg" alt="The formula age less than lifetime makes age 59 fresh and ages 60 and 61 stale. Stale entries send If-None-Match; a match returns 304, updates metadata and reuses the stored body, while a changed GET returns 200 with a new body. A fresh cached A can still be used after origin B is deployed." width="720" height="1540"&gt;&lt;figcaption&gt;Simplified age example: matching variant, no overriding request directive. no-cache still requires validation; must-revalidate forbids unvalidated stale reuse. Validator matching does not prove byte identity.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="freshness-how-long-to-reuse-without-asking"&gt;Freshness: how long to reuse without asking&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Cache-Control: max-age=60&lt;/code&gt; gives a response a freshness lifetime of 60 seconds. A cache compares that lifetime with the response&amp;rsquo;s current age, accounting for &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Age&lt;/code&gt;, transit time, and time stored. At exactly 60 seconds it is stale, not fresh for one more second. Shared caches prefer &lt;code&gt;s-maxage&lt;/code&gt; when present; absent explicit freshness, some responses can receive heuristic freshness.&lt;/p&gt;
&lt;p&gt;A fresh response is usually reusable, but matching the cache key and &lt;code&gt;Vary&lt;/code&gt; fields and obeying request/response directives still matters. &lt;code&gt;no-cache&lt;/code&gt; requires successful validation before reuse even when the response could otherwise be fresh. &lt;code&gt;no-store&lt;/code&gt; prohibits storage; it is not a synonym for revalidate. &lt;code&gt;public&lt;/code&gt; permits shared-cache storage in cases otherwise restricted; it does not specify a duration.&lt;/p&gt;
&lt;p&gt;For mutable HTML, a short explicit lifetime plus validation is often useful. It intentionally permits updates to take up to that lifetime to become visible through a compliant cache. A whole-tree deployment does not invalidate existing browser or CDN entries. Content-addressed assets can instead use a long lifetime when the URL changes with the bytes.&lt;/p&gt;
&lt;h2 id="validation-ask-whether-the-stored-representation-still-matches"&gt;Validation: ask whether the stored representation still matches&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;ETag&lt;/code&gt; is an opaque validator, not a checksum the client should decode. &lt;code&gt;Last-Modified&lt;/code&gt; is a timestamp validator. Neither header specifies an explicit freshness lifetime, and a server need not supply both. When heuristic freshness is permitted, however, a cache can use &lt;code&gt;Last-Modified&lt;/code&gt; to estimate a lifetime. For example, these illustrative headers express a lifetime and a validator separately:&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;Cache-Control: max-age=60, must-revalidate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ETag: &amp;#34;release-a&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After expiry, &lt;code&gt;If-None-Match: &amp;quot;release-a&amp;quot;&lt;/code&gt; lets a GET receive &lt;code&gt;304 Not Modified&lt;/code&gt; if the selected representation still matches, or a new &lt;code&gt;200&lt;/code&gt; with a body if it changed. The cache updates applicable metadata from the 304 and reuses its stored body. A 304 is not useful without a corresponding stored representation. &lt;code&gt;If-None-Match&lt;/code&gt; uses weak comparison, so successful validation does not necessarily prove byte-for-byte identity; &lt;code&gt;If-Range&lt;/code&gt; and combining partial responses have stronger requirements.&lt;/p&gt;
&lt;p&gt;The server can compute an ETag from bytes or other reliably changing metadata. A strong tag must distinguish representations whose data differs, including content codings where applicable. Sending the same strong ETag for different gzip and identity bytes is not fixed by adding a short lifetime. &lt;code&gt;Vary: Accept-Encoding&lt;/code&gt; also tells a cache which request header affected selection.&lt;/p&gt;
&lt;h2 id="a-small-exercise-that-separates-the-decisions"&gt;A small exercise that separates the decisions&lt;/h2&gt;
&lt;p&gt;This Python model deliberately excludes heuristic freshness, network delay, and variant selection. &lt;code&gt;age&lt;/code&gt; stands for the already calculated current age; the conditional request uses a server validator that has not changed. The model compares two fixed tag strings; it does not implement HTTP entity-tag parsing, tag lists, or the full weak-comparison rules.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lifetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stored_etag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#34;release-a&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;server_etag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#34;release-a&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;lifetime&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;reuse without validation&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;validate: 304, reuse body&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stored_etag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;server_etag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;validate: 200, replace body&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first line reuses without validation; the other two validate and reuse after a 304. Set &lt;code&gt;server_etag&lt;/code&gt; to &lt;code&gt;&amp;quot;release-b&amp;quot;&lt;/code&gt;: the stale cases become 200, while the 59-second case still reuses the stored response. That is the permitted visibility delay the freshness policy buys in exchange for lower latency and origin load.&lt;/p&gt;
&lt;h2 id="the-contract-worth-asserting"&gt;The contract worth asserting&lt;/h2&gt;
&lt;p&gt;Test matching and changed validators, freshness at the boundary, and the selected encoding. Also decide what should happen during an origin outage. HTTP can permit stale reuse under defined conditions or extensions; &lt;code&gt;must-revalidate&lt;/code&gt; prohibits reusing the response once stale without successful validation, even when disconnected. This increases correctness pressure at the cost of availability. A validator alone makes no such promise.&lt;/p&gt;
&lt;p&gt;A useful release record therefore states the cache policy, selected representation, observed conditional status, and tolerated update delay. Requiring two validator headers and one 304 cannot prove that every cache has current bytes, prevent all bandwidth waste, or replace testing the actual intermediary configuration.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9111.html"&gt;RFC 9111: HTTP Caching&lt;/a&gt;, sections 4.1–4.3 and 5.2 on variants, age, validation, and directives, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110: HTTP Semantics&lt;/a&gt;, sections 8.8, 13.1.2, and 13.1.5 on validators and comparison, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><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>