<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>踩坑 on Svtter's Blog</title><link>https://svtter.cn/tags/%E8%B8%A9%E5%9D%91/</link><description>Recent content in 踩坑 on Svtter's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 13 Jun 2026 18:29:24 +0800</lastBuildDate><atom:link href="https://svtter.cn/tags/%E8%B8%A9%E5%9D%91/index.xml" rel="self" type="application/rss+xml"/><item><title>Hugo 0.163 升级记录：security.allowContent 拦截了 HTML 内容</title><link>https://svtter.cn/p/hugo-0163-%E5%8D%87%E7%BA%A7%E8%AE%B0%E5%BD%95/</link><pubDate>Sat, 13 Jun 2026 18:29:24 +0800</pubDate><guid>https://svtter.cn/p/hugo-0163-%E5%8D%87%E7%BA%A7%E8%AE%B0%E5%BD%95/</guid><description>&lt;img src="https://svtter.cn/p/hugo-0163-%E5%8D%87%E7%BA%A7%E8%AE%B0%E5%BD%95/pics/cover_1781357169.png" alt="Featured image of post Hugo 0.163 升级记录：security.allowContent 拦截了 HTML 内容" /&gt;&lt;p&gt;最近更新了一次主题，不知不觉把 Hugo 也带到了 &lt;code&gt;v0.163.1&lt;/code&gt;。然后 &lt;code&gt;hugo server&lt;/code&gt; 就起不来了。&lt;/p&gt;
&lt;h2 id="现象"&gt;现象
&lt;/h2&gt;&lt;p&gt;启动直接报错：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR ... content/index.html:1:1&amp;#34;: access denied: &amp;#34;text/html&amp;#34; is not whitelisted in policy &amp;#34;security.allowContent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;首页 &lt;code&gt;content/index.html&lt;/code&gt;（一个嵌着 GitHub Calendar 的页面）被拒之门外。奇怪的是 markdown 文章一个没受影响。&lt;/p&gt;
&lt;h2 id="根因"&gt;根因
&lt;/h2&gt;&lt;p&gt;不是主题的锅，是 Hugo 本身。&lt;code&gt;0.163&lt;/code&gt; 引入了一个新的安全策略 &lt;code&gt;security.allowContent&lt;/code&gt;，默认配置长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;security&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="nx"&gt;allowContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;! ^text/html$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;意图是防止 &lt;code&gt;content/&lt;/code&gt; 目录里混入恶意 HTML（一种 XSS 防护）。&lt;code&gt;!&lt;/code&gt; 前缀表示 deny 规则。方向是对的，但本站的 HTML 内容页是正经页面，不是攻击向量，于是被误伤了。&lt;/p&gt;
&lt;h2 id="allowcontent-的语义挺绕"&gt;allowContent 的语义挺绕
&lt;/h2&gt;&lt;p&gt;这部分是最值得记的。我先按官方 security 文档那套 allowlist 的思路去猜，结果几次试错才搞清楚。把几次配置的结果放一起对比就很明显了：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;配置&lt;/th&gt;
					&lt;th style="text-align: center"&gt;html&lt;/th&gt;
					&lt;th style="text-align: center"&gt;markdown&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;默认 &lt;code&gt;['! ^text/html$']&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: center"&gt;拒&lt;/td&gt;
					&lt;td style="text-align: center"&gt;放行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;空列表 &lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: center"&gt;拒&lt;/td&gt;
					&lt;td style="text-align: center"&gt;放行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;['^text/html$']&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: center"&gt;放行&lt;/td&gt;
					&lt;td style="text-align: center"&gt;拒&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;据此可以推出几条结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置里&lt;strong&gt;没有任何 allow 规则&lt;/strong&gt;（只有 &lt;code&gt;!&lt;/code&gt; deny 规则，或者干脆为空）→ 「默认放行」模式，只拦截命中 deny 的类型；&lt;/li&gt;
&lt;li&gt;配置里&lt;strong&gt;一旦出现 allow 规则&lt;/strong&gt;（不带 &lt;code&gt;!&lt;/code&gt; 的项）→ 立刻切到「白名单」模式，只有命中的类型才放行，其余全部拒绝；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text/html&lt;/code&gt; 还有一条&lt;strong&gt;隐含的内置 deny&lt;/strong&gt;，连空列表 &lt;code&gt;[]&lt;/code&gt; 都清不掉它（所以 &lt;code&gt;[]&lt;/code&gt; 和默认表现一模一样）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就解释了为什么「显式放行 html」(&lt;code&gt;['^text/html$']&lt;/code&gt;) 反而把 markdown 也一起拒了——它触发了白名单模式。&lt;/p&gt;
&lt;p&gt;所以正确的修法是用一条通配 allow 规则：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;security&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="nx"&gt;allowContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;.*&lt;/code&gt; 命中所有类型，html 和 markdown 都放行；而且 allow 规则能压过那条内置的 html deny（实测 &lt;code&gt;['^text/html$']&lt;/code&gt; 时 html 是能通过的，证明 allow 优先级高于内置 deny）。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;顺带一提，直接读 &lt;code&gt;hugo config&lt;/code&gt; 打印出的当前生效配置，是确认这些细节最快的办法，比翻文档靠谱——毕竟官方 security 页面目前根本还没收录 &lt;code&gt;allowContent&lt;/code&gt; 这个新 key。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="修复"&gt;修复
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;config/_default/config.toml&lt;/code&gt; 末尾加上面那段，&lt;code&gt;hugo server&lt;/code&gt; 恢复正常。&lt;/p&gt;
&lt;h2 id="更大的坑contentindexhtml-让整站文章只剩四分之一"&gt;更大的坑：content/index.html 让整站文章只剩四分之一
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;allowContent&lt;/code&gt; 修完，&lt;code&gt;hugo server&lt;/code&gt; 确实能起了。但写这篇文章时发现一个&lt;strong&gt;比上面严重得多&lt;/strong&gt;的问题——新写的文章死活构建不出来，怎么都不生成独立页面。&lt;/p&gt;
&lt;p&gt;排查下来，又是 0.163 的行为变化。项目根目录有个 &lt;code&gt;content/index.html&lt;/code&gt;（starter 模板自带的，里面嵌了个 GitHub Calendar）。Hugo 一直有条警告：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Using index.html in your content&amp;rsquo;s root directory is usually incorrect &amp;hellip; your home page will be treated as a leaf bundle, meaning it won&amp;rsquo;t be able to have any child pages or sections.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;以前（旧版 Hugo）这条只是警告、不影响构建；&lt;strong&gt;0.163 开始动真格了&lt;/strong&gt;——首页一旦是 leaf bundle，整棵 content 树就丢了子页面/子 section。后果很吓人，拿掉这个文件前后对比：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;有 content/index.html&lt;/th&gt;
					&lt;th&gt;改成 _index.html 后&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;构建出的文章&lt;/td&gt;
					&lt;td&gt;~106 篇&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;625 篇&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;文章 URL&lt;/td&gt;
					&lt;td&gt;全跑去 &lt;code&gt;/post/&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;恢复 &lt;code&gt;/p/:slug/&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;新文章&lt;/td&gt;
					&lt;td&gt;静默吞掉&lt;/td&gt;
					&lt;td&gt;正常&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;hugo list all&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;只剩 1 条&lt;/td&gt;
					&lt;td&gt;715 条&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是说，&lt;strong&gt;如果直接拿 0.163 重新部署，站点会凭空消失四分之三的文章，所有旧的 &lt;code&gt;/p/&lt;/code&gt; 链接全 404&lt;/strong&gt;。这个杀伤力比 &lt;code&gt;allowContent&lt;/code&gt; 大多了，而且 &lt;code&gt;hugo server&lt;/code&gt; 照样能&amp;quot;启动成功&amp;quot;，不报错，极度隐蔽。&lt;/p&gt;
&lt;p&gt;最骚的是，这个 &lt;code&gt;content/index.html&lt;/code&gt; 里的日历&lt;strong&gt;从来就没在线上首页渲染过&lt;/strong&gt;（线上首页是文章列表），它纯粹是个&amp;quot;只搞破坏不干活&amp;quot;的死文件——来自 starter 模板，留着只会埋雷。&lt;/p&gt;
&lt;p&gt;修法很简单，二选一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;content/index.html&lt;/code&gt; 改名成 &lt;code&gt;content/_index.html&lt;/code&gt;（变成 branch bundle，不再吞子页面），构建立刻恢复；&lt;/li&gt;
&lt;li&gt;或者干脆删掉它，效果一样、更干净。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="另外带出来的两个小问题"&gt;另外带出来的两个小问题
&lt;/h2&gt;&lt;p&gt;排查过程里还冒出来两个，跟安全策略无关：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hugo --gc&lt;/code&gt; 报 permission denied&lt;/strong&gt;。&lt;code&gt;resources/_gen/&lt;/code&gt; 下的缓存文件是 root 拥有的——大概是之前用 root 或容器跑过一次构建留下的。&lt;code&gt;sudo chown -R $USER resources/&lt;/code&gt; 归位即可。这玩意儿只影响 &lt;code&gt;--gc&lt;/code&gt; 的垃圾回收，不影响 &lt;code&gt;hugo server&lt;/code&gt; 和普通构建。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一批配置弃用警告&lt;/strong&gt;（v0.158 起 deprecated）：&lt;code&gt;languageCode&lt;/code&gt; → &lt;code&gt;locale&lt;/code&gt;、&lt;code&gt;languages.*.languageName&lt;/code&gt; → &lt;code&gt;languages.*.label&lt;/code&gt;、&lt;code&gt;languages.*.languageDirection&lt;/code&gt; → &lt;code&gt;languages.*.direction&lt;/code&gt;。现在还是 WARN，未来版本会变成 ERROR。这个我开了个 &lt;a class="link" href="https://github.com/Svtter/hugo-blog/issues/72" target="_blank" rel="noopener"
 &gt;issue&lt;/a&gt; 跟踪，回头一并改了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="小结"&gt;小结
&lt;/h2&gt;&lt;p&gt;这次 0.163 升级踩了两个坑，一个明一个暗：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;明的&lt;/strong&gt;：&lt;code&gt;security.allowContent&lt;/code&gt; 默认拦 HTML，启动直接报错，反而好发现。加 &lt;code&gt;allowContent = ['.*']&lt;/code&gt; 解决。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;暗的&lt;/strong&gt;：&lt;code&gt;content/index.html&lt;/code&gt; 让首页变 leaf bundle，0.163 下静默吞掉四分之三的文章、打乱所有 URL，而 &lt;code&gt;hugo server&lt;/code&gt; 照样&amp;quot;成功启动&amp;quot;不报错。这个才是真正危险的——要不是我顺手写篇文章发现它构建不出来，直接部署就惨了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;教训：升级 Hugo 大版本后，别光看 &lt;code&gt;hugo server&lt;/code&gt; 能不能起，&lt;strong&gt;一定要对比一下构建出的文章数量和 URL 结构&lt;/strong&gt;有没有变（比如 &lt;code&gt;find public/p -type d | wc -l&lt;/code&gt;）。&lt;code&gt;hugo server&lt;/code&gt; 成功 ≠ 站点健康。&lt;/p&gt;
&lt;p&gt;至于那些弃用警告（&lt;code&gt;languageCode&lt;/code&gt; 之类），回头慢慢改，不影响运行。&lt;/p&gt;</description></item></channel></rss>