<?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>专题 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/essays/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/essays/index.xml" rel="self" type="application/rss+xml"/><item><title>可读的证据优先于仪表盘</title><link>https://www.xbcatm.com/zh/essays/readable-evidence-before-dashboards/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/essays/readable-evidence-before-dashboards/</guid><description>一个线上站点的健康，更适合用运维者能在终端读到的请求头来证明，而不是把同样事实聚合后带滞后的图表。</description><content:encoded>&lt;p&gt;监控仪表盘很方便，但方便不等于证据。本文论证：对小型静态服务，&amp;ldquo;站点是否健康&amp;quot;最可审计的形式，是一组运维者能阅读、能重跑、能粘进报告里的纯文本事实——而不是一个聚合的面板。&lt;/p&gt;
&lt;p&gt;区别很重要，因为两者回答的问题不同。仪表盘是众多样本之上的透镜，调教成让你看到趋势。探针是对公共边缘的一次单一、可复现的观察。当任务是证明一次发布或诊断一个具体请求时，探针是保真度更高的产物。&lt;/p&gt;
&lt;h2 id="仪表盘的聚合正是风险所在"&gt;仪表盘的聚合正是风险所在&lt;/h2&gt;
&lt;p&gt;仪表盘把成千上万条原始观察折成一系列聚合面板：平均延迟、错误率、可用性百分比。聚合正是审计线索变薄的地方。一个&amp;quot;99.98% 可用&amp;quot;的数字不会告诉你哪个请求失败、哪个请求头错了、今天的发布是否有被服务。除非你在别处保留原始日志，否则它是一个抹掉了产生它的证据的摘要。&lt;/p&gt;
&lt;p&gt;对&amp;quot;边缘服务的是我暂存的那棵树&amp;quot;这种信心属性，仪表盘用错了工具。这个属性由一次确定性的观察回答，而一条终端命令就能精确回答一次。&lt;/p&gt;
&lt;h2 id="一条可以读的探针"&gt;一条可以读的探针&lt;/h2&gt;
&lt;p&gt;站点附带一个小的只读辅助脚本 &lt;code&gt;downloads/check-http-headers.sh&lt;/code&gt;，以纯文本打印被服务的响应：&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;人读这一行，立刻同时知道三件事：状态是 200、类型是 HTML、无脚本策略确实在服务。把它与说&amp;quot;边缘 OK&amp;quot;的面板对比——面板要求你信任生成它的东西；请求头这一行本身就是证据。证书也用同样方式判定：把 &lt;code&gt;notAfter&lt;/code&gt; 读成一个日期，而不是面板上的一个颜色。&lt;/p&gt;
&lt;h2 id="用可核验的语句取代信号缺失"&gt;用可核验的语句取代信号缺失&lt;/h2&gt;
&lt;p&gt;本站偏好的不是仪表盘，而是在构建期间强制的契约，和发布窗口内一组确定性的命令。大致是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;离线检查校验产物（每一条内部链接、每一个 SVG、无脚本边界、校验和）。&lt;/li&gt;
&lt;li&gt;一条针对公共边缘的只读探针，一次调用确认状态、响应头与证书。&lt;/li&gt;
&lt;li&gt;一份字节清单把被服务的树与暂存的树挂钩，使&amp;quot;与暂存一致&amp;quot;成为哈希值而非感觉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每一处都是一个运维者能粘进 issue 或 changelog、未来的审计者能重跑的句子。它们都不需要面板才能被理解。&lt;/p&gt;
&lt;h2 id="什么时候你确实需要仪表盘"&gt;什么时候你确实需要仪表盘&lt;/h2&gt;
&lt;p&gt;这一切不是说观测无意义。如果你运维很多实例、聚合容量、或观察长期趋势，仪表盘是看清全局的正确视图。论断更窄：当问题只是&amp;quot;这一棵发布在公共边缘是否健康&amp;quot;时，答案存在于一次可复现的终端读取里，在它上面再叠一个面板只会增加延迟与聚合丢失，而不增加证据。&lt;/p&gt;
&lt;p&gt;决策启发式很简单：如果一条语句可以用一条命令检查、且其输出就是证明，就优先命令；只有在你真正需要聚合处才保留仪表盘。这能让站点自己的操作故事与站点的内容一样可审计。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl 文档&lt;/a&gt;，从单次调用读取响应头，已核验 2026-08-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/logs"&gt;十二要素方法：日志&lt;/a&gt;，把日志当作运维者可读的事件流，已核验 2026-08-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/concepts/observability-primer/"&gt;OpenTelemetry：可观测性基础&lt;/a&gt;，可观测性即高基数、带上下文的数据，已核验 2026-08-18。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>缓存校验是两只钟，不是一项新鲜度设置</title><link>https://www.xbcatm.com/zh/essays/caching-as-two-clocks/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/essays/caching-as-two-clocks/</guid><description>HTTP 缓存有新鲜度之钟与校验之钟；一个静态文件说明为什么两者都需要，以避免提供过期内容或重复下载字节。</description><content:encoded>&lt;p&gt;缓存是引入静默正确性 bug 最容易的地方，因为缓存看起来一直正确，直到它提供了与源不一致的字节。能把这个心智模型维持清楚的，是把每个缓存都视为解码两只独立的钟，而不是一个过期值。&lt;/p&gt;
&lt;p&gt;条件请求携带两类信息，把它们混为一谈就是陈旧内容上线的方式。一只是&lt;strong&gt;新鲜度&lt;/strong&gt;——一个响应可以不询问就重用多久。另一只是&lt;strong&gt;校验&lt;/strong&gt;——客户端如何询问它的副本是否仍然最新。本文通过本站点的实际响应来读这两只钟，并说明各自真正的用途。&lt;/p&gt;
&lt;h2 id="新鲜度多久不需要请求就能信任一个字节"&gt;新鲜度：多久不需要请求就能信任一个字节&lt;/h2&gt;
&lt;p&gt;新鲜度是决定一个请求是否直接从缓存命中的钟。它通常以 &lt;code&gt;Cache-Control&lt;/code&gt;（&lt;code&gt;max-age&lt;/code&gt;）或 &lt;code&gt;Expires&lt;/code&gt; 日期表达。在新鲜度窗口内，缓存直接返回存储的响应，从不接触源。它的工作是降低延迟与源负载；代价是在窗口内可能提供略微落后于源的版本。&lt;/p&gt;
&lt;p&gt;对静态站点，新鲜度窗口与不可变产物是吻合的：一次发布替换整棵树，因此短的新鲜度加一个校验器是安全、平淡的默认值。关键是新鲜度本身无法在过期后检测陈旧内容——那是第二只钟的工作。&lt;/p&gt;
&lt;h2 id="校验客户端如何询问它仍然是最新的吗"&gt;校验：客户端如何询问&amp;quot;它仍然是最新的吗？&amp;quot;&lt;/h2&gt;
&lt;p&gt;当新鲜度过期，客户端并不一定重新下载整个资源。校验就是条件请求：发送存储的标识，源要么返回新的表示，要么返回一个状态表明存储的副本仍然有效。HTTP 定义的两个标识是 &lt;code&gt;ETag&lt;/code&gt;（一个不透明校验器，通常由实体的字节派生）和 &lt;code&gt;Last-Modified&lt;/code&gt;（一个时间戳校验器）。&lt;/p&gt;
&lt;p&gt;本站的活动响应把两只钟都具体展示出来：&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;etag: &amp;#34;dkx84jwth7nkc9e&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, 24 Aug 2026 00:00:00 GMT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;针对该 ETag 的条件 &lt;code&gt;If-None-Match&lt;/code&gt; 请求产生 &lt;code&gt;304 Not Modified&lt;/code&gt;——校验器一致，因此客户端复用缓存副本而不重新下载正文。这就是第二只钟完成其工作：一次廉价的往返，而不是一次完整传输。&lt;/p&gt;
&lt;h2 id="为什么新鲜度与校验相互独立"&gt;为什么新鲜度与校验相互独立&lt;/h2&gt;
&lt;p&gt;两者必须分开理解，因为彼此并不蕴含。一个响应可以处于新鲜状态无需校验，然而校验标识仍然重要，为新鲜度过期那一刻做准备。反之，你也可以有强校验器，但一个导致几乎不变的资源做不必要重新验证的新鲜度策略。对静态文件，两者容易协调设定：短或公用的新鲜度、从产物字节派生的强 ETag，以及新鲜度一过客户端就走的重新验证路径。&lt;/p&gt;
&lt;p&gt;要警惕的失败模式是把两者折叠成&amp;quot;缓存过期了，所以重新下载&amp;quot;。那忽略了校验器，把每过一次期都变成一次完整传输。压缩后的产物让往返变小——本页 HTML 只传输几千字节——但浏览器校验器的原则（缓存拒绝陈旧，同时避免冗余的完整正文传输）与大小无关。&lt;/p&gt;
&lt;h2 id="值得断言的契约"&gt;值得断言的契约&lt;/h2&gt;
&lt;p&gt;一旦能分开命名新鲜度与校验，发布检查就能断言两者：被服务的响应头同时包含 &lt;code&gt;etag&lt;/code&gt; 与 &lt;code&gt;last-modified&lt;/code&gt;；新鲜度窗口足够短以保证诚实；当校验器匹配时，条件请求返回 &lt;code&gt;304&lt;/code&gt;。这三项检查描述了一个既不会静默提供长久陈旧字节、也不会浪费带宽的缓存。这是一个比仪表盘曲线更弱的证明词，而这正是它应该进决策日志而非监控图的原因。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching"&gt;MDN：HTTP 缓存&lt;/a&gt;，过期与校验作为不同机制，已核验 2026-08-14。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag"&gt;MDN：HTTP &lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;，条件请求校验，已核验 2026-08-14。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9111"&gt;RFC 9111：HTTP Caching&lt;/a&gt;，缓存过期与校验规则，已核验 2026-08-14。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>零 JavaScript 静态站是可辩护的选择</title><link>https://www.xbcatm.com/zh/essays/zero-js-as-a-defensible-choice/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/essays/zero-js-as-a-defensible-choice/</guid><description>为什么在一个内容站点上关闭脚本能力，是一项能用响应头、而不是凭口味证明的安全与可靠性决策。</description><content:encoded>&lt;p&gt;静态站点不需要 JavaScript 才能站得住脚；&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签的缺席本身就可能是重点。本文论证一个在线内容站点通过策略关闭脚本通道的工程依据，并用本站实际提供的请求头来证明这一论断。&lt;/p&gt;
&lt;p&gt;论证分三部分：你删掉的攻击面；让删除可被观察的契约；以及确实该放弃这一层的情况。&lt;/p&gt;
&lt;h2 id="你没有脚本通道就删掉了一整类威胁面"&gt;你没有脚本通道，就删掉了一整类威胁面&lt;/h2&gt;
&lt;p&gt;一个不执行自身或第三方脚本的页面，一次性移除整类失败与滥用。跨站脚本存在于能运行注入代码的文档中；一个根本不能运行代码的文档，注入没有任何可升级的落点。第三方统计与挂件因为根本没有 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 标签可以嵌入而消失。浏览器包依赖漂移带来的供应链风险也随之消失，因为没有包。每一类都是有限、可命名的表面——也就是&amp;quot;小系统&amp;quot;那种&amp;quot;选择你能说清的边界&amp;quot;的特性。&lt;/p&gt;
&lt;p&gt;这不是声称站点&amp;quot;安全&amp;quot;，而是声称攻击面更窄，并且关键的是更容易推理，因为它是靠构造枚举的，而不是靠审计枚举的。&lt;/p&gt;
&lt;h2 id="让边界可强制执行的响应头"&gt;让边界可强制执行的响应头&lt;/h2&gt;
&lt;p&gt;口味不是策略。这里的区别是，无脚本契约是由服务器强制执行的，而不是靠开发者的自觉。本站的活动响应带：&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;其中 &lt;code&gt;script-src 'none'&lt;/code&gt; 指令是可强制执行的部分。即使某个未来的发布在页面里漏进一个多余的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;，浏览器也拒绝执行它。因此&amp;quot;没有 JavaScript&amp;quot;是一项被服务的契约，每次构建都由校验器检查（&lt;code&gt;script-free&lt;/code&gt;），而不是一次希望。这就是&amp;quot;我们不用 JS&amp;quot;和&amp;quot;公共边缘拒绝运行 JS&amp;quot;的区别。&lt;/p&gt;
&lt;h2 id="移除之后静态结构带来的好处"&gt;移除之后静态结构带来的好处&lt;/h2&gt;
&lt;p&gt;没有脚本之后，内容模型回归到更接近已发布文档的东西。缓存更简单，因为静态文件有稳定的字节身份和一个 ETag，而不是一个可变的 DOM。在终端里读线上 HTML、用&amp;quot;查看源代码&amp;quot;、或索引整棵树都很直接，因为产量就是磁盘上的普通文件。这是这个站点接受的交换：没有交互状态、没有客户端渲染、没有需要视图模型的反馈。对文档与运维站点而言，这是正确的取舍，值得写下来。&lt;/p&gt;
&lt;h2 id="什么时候零-js的论断是错的"&gt;什么时候&amp;quot;零 JS&amp;quot;的论断是错的&lt;/h2&gt;
&lt;p&gt;这是一层功能，不是一张纯净的标签。一个真正需要客户端交互的站点——实时仪表盘、文档编辑器、支付流程——不应该为了迁就约束就扭曲成提供静态 HTML 再硬塞变通方案。错误的应用是削足适履地把产品切成适应约束的形状。正确的应用是认识到：用来阅读、检索、归档的内容，从&amp;quot;成为文档&amp;quot;中收益；只有在文档本身不足时才需要脚本。&lt;/p&gt;
&lt;p&gt;对这一性质，被服务的响应头就是证据，任何一次发布都可以通过读取公共边缘复现它。这正是偏好与已部署的决策之间的差别。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&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：内容安全策略（&lt;code&gt;script-src&lt;/code&gt;）&lt;/a&gt;，禁用脚本执行的指令，已核验 2026-08-13。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://portswigger.net/web-security/cross-site-scripting"&gt;PortSwigger：什么是跨站脚本（XSS）？&lt;/a&gt;，脚本无法运行时被移除的攻击类别，已核验 2026-08-13。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-community/controls/Content_Security_Policy"&gt;OWASP：内容安全策略&lt;/a&gt;，CSP 作为纵深防御控件，已核验 2026-08-13。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>