<?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/tutorials/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tutorials/index.xml" rel="self" type="application/rss+xml"/><item><title>发布一个静态站点并证明边缘一致</title><link>https://www.xbcatm.com/zh/tutorials/ship-a-static-release/</link><pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/tutorials/ship-a-static-release/</guid><description>从构建到公共边缘确认的完整发布演练，用检查把暂存树变成已发布、已验证的产物。</description><content:encoded>&lt;p&gt;一次发布，只有当公共边缘与你选择的文件一致时才完成——而不是当最后一条上传命令退出码为零。本教程把这个句子变成可重复的过程，使用 release-and-recovery 系列笔记描述的检查。到最后阶段之前，一切都安全地在本地进行；唯一的远端步骤是结尾的只读探针。&lt;/p&gt;
&lt;p&gt;工作流有四个阶段，每个阶段产生下一阶段消费的证据：&lt;strong&gt;构建&lt;/strong&gt;、&lt;strong&gt;校验&lt;/strong&gt;、&lt;strong&gt;发布&lt;/strong&gt;、&lt;strong&gt;验证&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;开始前先让三个事实变得明确，让失败变得廉价：如何运行构建（&lt;code&gt;make build&lt;/code&gt;）、活动站点服务的是哪个单一目录、如何把那个目录重命名回上一个树。先把这三个答案写下来，就能把整个过程从一次豪赌变成带可见回滚的脚本。其余教程假设你能回答它们。&lt;/p&gt;
&lt;h2 id="1-构建单一可复现的产物"&gt;1. 构建单一可复现的产物&lt;/h2&gt;
&lt;p&gt;从干净的控制树开始，而不是服务器上目前的东西。&lt;code&gt;small-systems&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;$ make build
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Pages │ 130 │ 128
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Static files │ 42 │ 42
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Total in 171 ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;构建会把指纹写入 &lt;code&gt;data/buildinfo.toml&lt;/code&gt;——源提交与构建时间——首页会渲染它。把指纹留在产物内，让后来的读者能证明哪棵源树产生了这些字节，审计时不必猜测。&lt;/p&gt;
&lt;h2 id="2-针对离线产物契约做验证"&gt;2. 针对离线产物契约做验证&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;$ make check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;validated 257 HTML documents; internal targets and script-free contract are intact
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;验证器验证每一张生成的 SVG、遍历每一个内部链接与片段、强制无 JS 契约，并校验下载项校验和。它在坏产物上失败，而不是在慢网络上失败——这正是 &lt;code&gt;reliable-defaults&lt;/code&gt; 所说的把主路径变得无聊。&lt;/p&gt;
&lt;h2 id="3-为每个字节命名清单"&gt;3. 为每个字节命名清单&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; public &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find . -type f &lt;span class="p"&gt;|&lt;/span&gt; sort &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;&amp;#39;s#^./##&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs sha256sum &amp;gt; /tmp/release.sha256
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ grep robots.txt /tmp/release.sha256
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;robots.txt f6ad425cbc898cef00d4b62bef6b17dc627de9ee0c910b31dcc1ee4d054f3d24
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;release-manifest&lt;/code&gt; 笔记为每个已发布字节命名，使&amp;quot;边缘一致&amp;quot;的声明可以逐字节核对。在整个发布窗口内保留此清单；替换后要和线上那棵树一比。&lt;/p&gt;
&lt;h2 id="4-通过交换完整目录来发布"&gt;4. 通过交换完整目录来发布&lt;/h2&gt;
&lt;p&gt;不要逐文件覆盖线上树。先在现有树旁边暂存完整树，然后交换 Caddy 服务的那个单一目录名——&lt;code&gt;atomic-switch&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;$ rsync -a public/ /srv/www/example-site.new/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ mv /srv/www/example-site.new /srv/www/example-site
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因为服务根是一个目录名，交换是瞬时的，绝不会出现写到一半。旧树还在，因此回滚就是朝反方向的一个 &lt;code&gt;rename&lt;/code&gt;（&lt;code&gt;reliable-defaults&lt;/code&gt;）。&lt;/p&gt;
&lt;h2 id="5-确认公共边缘与你暂存的一致"&gt;5. 确认公共边缘与你暂存的一致&lt;/h2&gt;
&lt;p&gt;在信任替换之前，先证明线上状态与头部与产物匹配。&lt;code&gt;read-only-release-smoke-tests&lt;/code&gt; 与 &lt;code&gt;read-public-edge&lt;/code&gt; 两篇笔记都把这一步作为最后的关卡——运行这个 helper 并读它的输出：&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;$ ./downloads/check-http-headers.sh https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&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;; 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;带正确 CSP 与内容类型的 &lt;code&gt;200&lt;/code&gt; 是第一个一致。第二个一致是字节：把线上树哈希，与第 3 步的清单比较。匹配时，边缘就是你暂存的树。如果线上内容不同，停下——你看的是错的树。&lt;/p&gt;
&lt;h2 id="6-结束前确认证书窗口"&gt;6. 结束前确认证书窗口&lt;/h2&gt;
&lt;p&gt;发布还没有完成直到证书也健康。&lt;code&gt;check-tls-expiry&lt;/code&gt; 在不破坏 TLS 会话的情况下读取有效期窗口：&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;$ ./downloads/check-tls-expiry.sh www.xbcatm.com 30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subject=CN=www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;issuer=C=US, O=Let&amp;#39;s Encrypt, CN=YE1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notBefore=Aug 21 08:37:13 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notAfter=Nov 19 08:37:12 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;certificate remains valid for at least 30 day(s)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;临近过期的证书会把&amp;quot;成功&amp;quot;发布变成迫在眉睫的事件。&lt;code&gt;certificate-expiry&lt;/code&gt; 笔记与发布清单都把 TLS 窗口当作发布条件的一部分，而不是后来的意外。把 &lt;code&gt;notAfter&lt;/code&gt; 读成具体日期，而不是仪表盘颜色。&lt;/p&gt;
&lt;h2 id="7-记住-dns-缓存窗口"&gt;7. 记住 DNS 缓存窗口&lt;/h2&gt;
&lt;p&gt;如果你的发布改了 DNS 记录（主机变更、CAA 更新或新的边缘），记住传播遵循一个不由你掌握的进度表。&lt;code&gt;dns-change-window&lt;/code&gt; 说明为什么回滚必须包含缓存感知的窗口，而不只是那条记录。一个在本地看起来完整的发布，在远端解析器上可能仍处于传播中途，长达旧 TTL 那么久。&lt;/p&gt;
&lt;h2 id="8-备份一条可恢复的路径而不只是字节"&gt;8. 备份一条可恢复的路径，而不只是字节&lt;/h2&gt;
&lt;p&gt;发布是恢复的另一面。发布前，确认存在只做一次恢复演练的记录——&lt;code&gt;backup-rehearsal&lt;/code&gt; 强调备份只有在从它恢复过一次之后才有用。本教程假设&amp;quot;恢复&amp;quot;那个周期步骤已经能工作，因为恢复是让发布可逆而非有风险的那一半。&lt;/p&gt;
&lt;h2 id="9-记录后来者可以审计的结果"&gt;9. 记录后来者可以审计的结果&lt;/h2&gt;
&lt;p&gt;当替换完成、窗口检查完毕，留下后续操作者能信赖的事实：commit、清单哈希、两个探针输出。&lt;code&gt;postmortem&lt;/code&gt; 条目在指名事实——哪些字节、哪个状态、哪些日期——时，比断言&amp;quot;它工作过&amp;quot;更有用。把同样的结果写进 changelog，让站点自身的演进保持可审计。&lt;/p&gt;
&lt;h2 id="路径对应到系列"&gt;路径对应到系列&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th&gt;证据&lt;/th&gt;
&lt;th&gt;笔记&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;构建&lt;/td&gt;
&lt;td&gt;buildinfo 指纹&lt;/td&gt;
&lt;td&gt;small-systems, atomic-switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;校验&lt;/td&gt;
&lt;td&gt;offline verify 通过&lt;/td&gt;
&lt;td&gt;reliable-defaults, release-checklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;清单&lt;/td&gt;
&lt;td&gt;每个文件 SHA-256&lt;/td&gt;
&lt;td&gt;release-manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;发布&lt;/td&gt;
&lt;td&gt;完整目录交换&lt;/td&gt;
&lt;td&gt;atomic-switch, caddy-static-files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;验证&lt;/td&gt;
&lt;td&gt;HEAD 一致 + 清单哈希&lt;/td&gt;
&lt;td&gt;read-only-smoke-tests, public-edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;证书&lt;/td&gt;
&lt;td&gt;有效期日期&lt;/td&gt;
&lt;td&gt;certificate-expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;记录&lt;/td&gt;
&lt;td&gt;commit + 探针输出&lt;/td&gt;
&lt;td&gt;postmortem, changelog&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每个阶段消费前一阶段的证据。任一段失败你都要停下，旧树仍是线上服务的那棵——这正是让整个过程可以安全练习的性质。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/coreutils/manual/html_node/sha2-utilities.html"&gt;GNU Coreutils: sha2 utilities&lt;/a&gt;，文件级校验与清单，已核验 2026-08-11。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html"&gt;POSIX &lt;code&gt;rename()&lt;/code&gt;&lt;/a&gt;，完整目录发布，已核验 2026-08-11。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/file_server"&gt;Caddy &lt;code&gt;file_server&lt;/code&gt;&lt;/a&gt;，根选择与交换，已核验 2026-08-11。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，证书日期，已核验 2026-08-11。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>像客户端一样读取活动边缘</title><link>https://www.xbcatm.com/zh/tutorials/http-edge-walkthrough/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/tutorials/http-edge-walkthrough/</guid><description>对静态站承诺的每条 HTTP 契约做一次完整巡检，用的都是只读的 curl 与 openssl 命令，可对任意端点运行。</description><content:encoded>&lt;p&gt;HTTP 基础篇的每条笔记各取一纸契约并展示单独的证据。本教程则按顺序走完一次客户端请求触达的完整路径，只用只读命令。到结束时，你已经在任何活动静态边缘上复现了那些笔记描述的检查——不改变它的状态。&lt;/p&gt;
&lt;p&gt;我们全程用 &lt;code&gt;www.xbcatm.com&lt;/code&gt;。把它换成你要检查的主机。下面每条命令都是只读的：读头部、请求元数据，或要求特定字节范围；没有任何一条会写入源站。&lt;/p&gt;
&lt;h2 id="1-先确定方法可以问什么"&gt;1. 先确定方法可以问什么&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;$ curl -s -o /dev/null -w &amp;#39;OPTIONS -&amp;gt; HTTP %{http_code}\n&amp;#39; -X OPTIONS https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OPTIONS -&amp;gt; HTTP 405
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;405 Method Not Allowed&lt;/code&gt; 是诚实的答案。&lt;code&gt;http-method-semantics&lt;/code&gt; 笔记解释了为什么这是静态源站应保持的契约：每个方法要么按文档化语义被支持，要么用表明拒绝的状态码回答。危险的失败模式是代理或导入的 handler 把被拒绝的方法转成静默的 200。如果你的边缘对 &lt;code&gt;POST&lt;/code&gt; 也回 200，承诺就不再可见。&lt;/p&gt;
&lt;h2 id="2-读取表示行"&gt;2. 读取表示行&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -D - -o /dev/null https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;accept-ranges: bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: &lt;span class="m"&gt;15890&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; base-uri &lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; form-action &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; frame-ancestors &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; object-src &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; script-src &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; style-src &lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; img-src &lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt; data:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;etag: &lt;span class="s2"&gt;&amp;#34;dkx84jwth7nkc9e&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, &lt;span class="m"&gt;24&lt;/span&gt; Aug &lt;span class="m"&gt;2026&lt;/span&gt; 13:55:23 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;strict-transport-security: max-age&lt;span class="o"&gt;=&lt;/span&gt;31536000&lt;span class="p"&gt;;&lt;/span&gt; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-content-type-options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三个头部值得停下来。&lt;code&gt;content-type: text/html; charset=utf-8&lt;/code&gt; 命中了媒体类型，&lt;code&gt;content-type-and-nosniff&lt;/code&gt; 说明为什么它必须描述你真正发送的字节。&lt;code&gt;etag: &amp;quot;dkx84jwth7nkc9e&amp;quot;&lt;/code&gt; 交给你强验证器。&lt;code&gt;x-content-type-options: nosniff&lt;/code&gt; 要求浏览器不要猜测类型。每一个都是证据而不是装饰，合在一起是说：这个边缘在你信任正文之前，就精确告诉你它的策略。&lt;/p&gt;
&lt;h2 id="3-确认-head-等于-get-但不带正文"&gt;3. 确认 HEAD 等于 GET 但不带正文&lt;/h2&gt;
&lt;p&gt;HEAD 必须安全，并返回与 GET 相同的元数据，但不带正文。比较 HEAD 的 &lt;code&gt;Content-Length&lt;/code&gt;（GET 会返回的字节数）与你真正 READ 到的字节：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -sI https://www.xbcatm.com/ &lt;span class="p"&gt;|&lt;/span&gt; grep -i content-length
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: &lt;span class="m"&gt;15890&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s https://www.xbcatm.com/ &lt;span class="p"&gt;|&lt;/span&gt; wc -c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;15890&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两者一致：都是 15890。这就是 &lt;code&gt;head-request-parity&lt;/code&gt; 契约。若代理在 HEAD 上去掉或改写 &lt;code&gt;Content-Length&lt;/code&gt;，这样一对检查就能抓住它。这正是 &lt;code&gt;check-http-headers.sh&lt;/code&gt; 用 curl 一起读状态与头部、发布冒烟测试在替换前后比较 HEAD 的原因。&lt;/p&gt;
&lt;h2 id="4-用范围请求精确的字节"&gt;4. 用范围请求精确的字节&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -H &lt;span class="s1"&gt;&amp;#39;Range: bytes=0-99&amp;#39;&lt;/span&gt; -w &lt;span class="s1"&gt;&amp;#39;HTTP %{http_code} bytes=%{size_download}\n&amp;#39;&lt;/span&gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP &lt;span class="m"&gt;206&lt;/span&gt; &lt;span class="nv"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对 100 字节区间给出 &lt;code&gt;206&lt;/code&gt; 与 100 字节是诚实的结果。&lt;code&gt;http-range-requests&lt;/code&gt; 笔记说明服务器如何选择字节、以及为什么需要强验证器。若服务器对任何范围区间都回 &lt;code&gt;200&lt;/code&gt; 全量正文，那是另一种更昂贵的契约，也说明源站并不追踪你要的字节身份。&lt;/p&gt;
&lt;h2 id="5-用验证器让缓存重新验证"&gt;5. 用验证器让缓存重新验证&lt;/h2&gt;
&lt;p&gt;现在把方法、表示与字节选择通过缓存结合。存储副本只有在验证器仍然匹配时才是最新。把 ETag 条件式重放：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -w &lt;span class="s1"&gt;&amp;#39;HTTP %{http_code}\n&amp;#39;&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;If-None-Match: &amp;#34;dkx84jwth7nkc9e&amp;#34;&amp;#39;&lt;/span&gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP &lt;span class="m"&gt;304&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;304 Not Modified&lt;/code&gt; 只带极小的头部回复，告诉你所问的副本仍然是答案。这就是 &lt;code&gt;conditional-requests&lt;/code&gt; 契约：验证决定存储副本是否新鲜；&lt;code&gt;http-cache-headers&lt;/code&gt; 描述两个时钟——来自缓存头部的新鲜度，与来自验证器的正确性。&lt;code&gt;304&lt;/code&gt; 从不是错误，它是&amp;quot;你已有正确的字节&amp;quot;的廉价说法。当 ETag 变化，下一次重新验证会返回新的 &lt;code&gt;200&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="6-协商更小的表示"&gt;6. 协商更小的表示&lt;/h2&gt;
&lt;p&gt;同样的内容在客户端表明能解压时可以传递更少字节。请求 gzip 并观察正文缩水而语义不变：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -w &lt;span class="s1"&gt;&amp;#39;no-enc: %{size_download} bytes\n&amp;#39;&lt;/span&gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -H &lt;span class="s1"&gt;&amp;#39;Accept-Encoding: gzip&amp;#39;&lt;/span&gt; -w &lt;span class="s1"&gt;&amp;#39;gzip: %{size_download} bytes\n&amp;#39;&lt;/span&gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;no-enc: &lt;span class="m"&gt;15890&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gzip: &lt;span class="m"&gt;4368&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;普通 GET 返回 15890 字节；gzip 协商对同一内容返回 4368。这是 &lt;code&gt;http-compression-negotiation&lt;/code&gt; 笔记的核心：改变选中的表示会改变线上字节，元数据（&lt;code&gt;content-length&lt;/code&gt;、&lt;code&gt;encoding&lt;/code&gt;）也随之改变。请求压缩的客户端与存储编码形式的缓存必须就选了哪种表示达成一致，否则缓存会交付不匹配的结果。&lt;/p&gt;
&lt;h2 id="7-跟随-url-变化时的故事"&gt;7. 跟随 URL 变化时的故事&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s -o /dev/null -w &lt;span class="s1"&gt;&amp;#39;%{http_code} -&amp;gt; %{redirect_url}\n&amp;#39;&lt;/span&gt; -I http://www.xbcatm.com/ &lt;span class="p"&gt;|&lt;/span&gt; head -1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;308&lt;/span&gt; -&amp;gt; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;308 Permanent Redirect&lt;/code&gt; 保持方法与故事，正是 &lt;code&gt;redirects-and-status-codes&lt;/code&gt; 关心的。无论是永久移动的 301/308，还是由站点处理的 404，状态行都是故事的第一句，应在读正文前先读。&lt;/p&gt;
&lt;h2 id="8-最后让爬虫读地图"&gt;8. 最后让爬虫读地图&lt;/h2&gt;
&lt;p&gt;边缘也会被根本不请求 HTML 的机器读取。发现机制回答两个不同的问题，&lt;code&gt;robots-and-sitemaps&lt;/code&gt; 有说明。确认发现机制指向真实 sitemap：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s https://www.xbcatm.com/robots.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User-agent: *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disallow:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sitemap: https://www.xbcatm.com/sitemap.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一个在 robots、sitemap 与 HTTP 头部上都回答同一主机的主机，其操作者在发布前读过公共边缘。&lt;/p&gt;
&lt;h2 id="所有检查一览"&gt;所有检查一览&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;检查&lt;/th&gt;
&lt;th&gt;证明什么&lt;/th&gt;
&lt;th&gt;笔记&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPTIONS&lt;/code&gt; -&amp;gt; 405&lt;/td&gt;
&lt;td&gt;源站拒绝不支持的方法&lt;/td&gt;
&lt;td&gt;http-method-semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content-type&lt;/code&gt; + &lt;code&gt;nosniff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;头部描述真实字节&lt;/td&gt;
&lt;td&gt;content-type-and-nosniff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEAD == GET Content-Length&lt;/td&gt;
&lt;td&gt;安全、HEAD 一致&lt;/td&gt;
&lt;td&gt;head-parity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range 0-99 -&amp;gt; 206/100&lt;/td&gt;
&lt;td&gt;精确字节选择&lt;/td&gt;
&lt;td&gt;http-range-requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ETag&lt;/code&gt; -&amp;gt; 304&lt;/td&gt;
&lt;td&gt;验证器决定新鲜度&lt;/td&gt;
&lt;td&gt;conditional-requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;308 保留方法&lt;/td&gt;
&lt;td&gt;重定向保留故事&lt;/td&gt;
&lt;td&gt;redirects-and-status-codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;robots -&amp;gt; sitemap&lt;/td&gt;
&lt;td&gt;发现答案不同&lt;/td&gt;
&lt;td&gt;robots-and-sitemaps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每一行原来是一条笔记；现在是你可对任意边缘运行的命令。需要细节时读链接的笔记，把这个表留作再次检查时的速查。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，方法、验证器与状态码，已核验 2026-08-07。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9111.html"&gt;RFC 9111 — HTTP Caching&lt;/a&gt;，新鲜度与重新验证，已核验 2026-08-07。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7538.html"&gt;RFC 7538 — 308 Permanent Redirect&lt;/a&gt;，保留方法的重定向，已核验 2026-08-07。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl 手册&lt;/a&gt;，只读检查选项，已核验 2026-08-07。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>审计公共边缘的信任</title><link>https://www.xbcatm.com/zh/tutorials/audit-a-public-edge/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/tutorials/audit-a-public-edge/</guid><description>一次只读演练，像细心运维那样检查安全头、TLS 身份、证书链、主机密钥、CAA 与 Cookie 作用域。</description><content:encoded>&lt;p&gt;web-security 系列把安全设置当作带作用域与失败模式的契约。本教程以只读方式、端到端地对一个线上主机跑这些契约，并告诉你每个答案的含义。它不改变服务器，只检查服务器愿意对任何人说的话。下面全程用 &lt;code&gt;www.xbcatm.com&lt;/code&gt;，请换成你自己的主机。&lt;/p&gt;
&lt;h2 id="1-从边缘宣告的头部开始"&gt;1. 从边缘宣告的头部开始&lt;/h2&gt;
&lt;p&gt;第一个契约是浏览器被告知允许什么。用 &lt;code&gt;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;$ curl -s -D - -o /dev/null https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&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-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;span class="line"&gt;&lt;span class="cl"&gt;strict-transport-security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-content-type-options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-frame-options: DENY
&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;content-security-policy&lt;/code&gt; 是最强的，&lt;code&gt;security-headers&lt;/code&gt; 说明为什么策略应匹配实际页面。带 &lt;code&gt;includeSubDomains&lt;/code&gt; 的 &lt;code&gt;strict-transport-security&lt;/code&gt; 是传输承诺，&lt;code&gt;hsts-rollout&lt;/code&gt; 警告它必须分步推进，因为它具有粘性。&lt;code&gt;nosniff&lt;/code&gt; 阻止 MIME 类型猜测。你以为讲的是真的却没有验证过的头部，是 bug 不是特性。&lt;/p&gt;
&lt;p&gt;第一步是静态的，但它是浏览器最先看到的契约。对静态站，&lt;code&gt;script-src 'none'&lt;/code&gt; 既安全又诚实：没有可执行的，就什么都不运行。重要的习惯是把你&lt;em&gt;相信&lt;/em&gt;的策略与边缘实际发送的对比。代理增加或去除某个头部就是静默改变契约；只读 fetch 能抓住这种漂移。若你用 &lt;code&gt;check-http-headers.sh&lt;/code&gt;，它把状态与头部一起读，让策略与可达性声明留在同一条记录里。&lt;/p&gt;
&lt;h2 id="2-确认身份是真的而非猜测"&gt;2. 确认身份是真的，而非猜测&lt;/h2&gt;
&lt;p&gt;TLS 身份始于叶证书。读它确切的主体、签发者与有效期：&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;$ echo | openssl s_client -connect www.xbcatm.com:443 -servername www.xbcatm.com 2&amp;gt;/dev/null \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | openssl x509 -noout -subject -issuer -dates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subject=CN=www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;issuer=C=US, O=Let&amp;#39;s Encrypt, CN=YE1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notBefore=Aug 21 08:37:13 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notAfter=Nov 19 08:37:12 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;主体是确切主机名而不是相邻站点的通配符，&lt;code&gt;notAfter&lt;/code&gt; 是具体日期。&lt;code&gt;certificate-expiry&lt;/code&gt; 把那个日期当作发布关卡而不是仪表盘颜色。操作者应能凭上次审计的记忆报出 &lt;code&gt;notAfter&lt;/code&gt;，而不是在续期时才发觉。&lt;/p&gt;
&lt;h2 id="3-读链而不只是叶"&gt;3. 读链，而不只是叶&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;$ echo | openssl s_client -connect www.xbcatm.com:443 -servername www.xbcatm.com -showcerts 2&amp;gt;/dev/null | grep &amp;#39;BEGIN CERT&amp;#39; | wc -l
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;带其叶与中间证书时，这个数应为 &lt;code&gt;4&lt;/code&gt;。&lt;code&gt;certificate-chain&lt;/code&gt; 笔记解释原因：叶必须由你实际授权的中间签发，边缘必须发送那个中间，深度验证的客户端才会成功。对公共 CA 而言，恰好为 1 是危险信号。&lt;/p&gt;
&lt;p&gt;第二个要检查链的原因：第 2 步读到的身份只证明叶，不证明客户端能到达它信任的根。链是补上那个缺口的环节。如果边缘某天开始发送不同的中间证书——比如某天一个测试 CA——&lt;code&gt;issuer&lt;/code&gt; 与数量会在页面上任何元素变化之前改变。读链是发现信任发生迁移的最廉价方式。&lt;/p&gt;
&lt;h2 id="4-用一个验证器证明新鲜度完好"&gt;4. 用一个验证器证明新鲜度完好&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;conditional-requests&lt;/code&gt; 笔记在这里同样适用：活动边缘应对重新验证回 &lt;code&gt;304&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;$ curl -s -o /dev/null -w &amp;#39;HTTP %{http_code}\n&amp;#39; -H &amp;#39;If-None-Match: &amp;#34;dkx84jwth7nkc9e&amp;#34;&amp;#39; https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP 304
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因为本站坚持无脚本策略，CSP 在发布之间从不变化；只有字节变化时 &lt;code&gt;etag&lt;/code&gt; 才动。&lt;/p&gt;
&lt;h2 id="5-检查哪个-ca-被允许签发"&gt;5. 检查哪个 CA 被允许签发&lt;/h2&gt;
&lt;p&gt;TLS 身份与 DNS 策略携手。确认 CAA 记录授权你实际使用的签发者，这样只有那个 CA 能合法为域名签名：&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;$ dig +short CAA www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;dns-caa-records&lt;/code&gt; 把 CAA 当作信任的供应商：它授权签发者，使证书链审计变得有意义。若无 CAA，任何 CA 都能签发；有 CAA，第 3 步的中间 &lt;em&gt;应该&lt;/em&gt;能追溯到被授权的签发者。若 &lt;code&gt;dig&lt;/code&gt; 返回空，意味着没有任何 CA 被限制——这本身是审计结论。&lt;/p&gt;
&lt;h2 id="6-检查谁持有主机密钥"&gt;6. 检查谁持有主机密钥&lt;/h2&gt;
&lt;p&gt;链保护 TLS，但另一条信任边界是 SSH 访问。主机密钥是你 SSH 进去的那台服务器的部署记录。&lt;code&gt;ssh-host-key&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;$ ssh-keygen -lf ~/.ssh/known_hosts | grep www.xbcatm.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确认指纹与你当初记录主机时的一致。重建后主机密钥变化，可能是合法的重装，也可能是该停下的理由——这正是主机密钥策略作为 web-security 边界存在的原因，而不只是便利。&lt;/p&gt;
&lt;h2 id="7-让-cookie-作用域比应用更窄"&gt;7. 让 Cookie 作用域比应用更窄&lt;/h2&gt;
&lt;p&gt;即使无脚本静态站也可能设置 cookie；若它设置了，其作用域就是一种安全声明。&lt;code&gt;cookie-scope&lt;/code&gt; 说 cookie 的主机、路径、传输与同站边界应比应用触达更窄。审计不要让 cookie 以超出必要的范围设置，对敏感内容优先 &lt;code&gt;Secure; HttpOnly; SameSite&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="把审计当清单"&gt;把审计当清单&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;信号&lt;/th&gt;
&lt;th&gt;读作&lt;/th&gt;
&lt;th&gt;笔记&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSP &lt;code&gt;script-src 'none'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无脚本策略在线&lt;/td&gt;
&lt;td&gt;security-headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HSTS &lt;code&gt;includeSubDomains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传输承诺是粘性的&lt;/td&gt;
&lt;td&gt;hsts-rollout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;证书 subject+到期&lt;/td&gt;
&lt;td&gt;身份是具体日期&lt;/td&gt;
&lt;td&gt;certificate-expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;链显示 &lt;code&gt;4&lt;/code&gt; 张证书&lt;/td&gt;
&lt;td&gt;中间证书被发送&lt;/td&gt;
&lt;td&gt;certificate-chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重新验证回 &lt;code&gt;304&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓存验证器工作&lt;/td&gt;
&lt;td&gt;conditional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAA 被授权&lt;/td&gt;
&lt;td&gt;只有 CA 能签发&lt;/td&gt;
&lt;td&gt;dns-caa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主机密钥指纹一致&lt;/td&gt;
&lt;td&gt;SSH 信任是一条记录&lt;/td&gt;
&lt;td&gt;ssh-host-key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每个信号原来是一条笔记；合起来它们是一份清单。在不到一分钟里用两三条只读命令就能全部加完，服务器分毫未动。&lt;/p&gt;
&lt;h2 id="常规建议"&gt;常规建议&lt;/h2&gt;
&lt;p&gt;这个审计应属于定时检查，而不只在发布时。若你已有的一套检查，把这两条 &lt;code&gt;openssl&lt;/code&gt; 行与头部抓取加进去。把输出按日期开头的文件名保留，让后来的比较能显示漂移。这样即使什么都没变，静态站点也能保持证据优先。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，验证器与条件请求，已核验 2026-08-06。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797 — HSTS&lt;/a&gt;，传输策略，已核验 2026-08-06。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc5280.html"&gt;RFC 5280 — X.509 PKI&lt;/a&gt;，证书链，已核验 2026-08-06。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，链与身份检查，已核验 2026-08-06。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linux.die.net/man/1/dig"&gt;dig(1)&lt;/a&gt;，DNS 记录查找，已核验 2026-08-06。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>