fix(security): 页面文案 markdown 管线加标签/属性/协议净化(P0)

方案 10 §7.1 实锤漏洞:renderMarkdown() 用无参 createMarkdownProcessor,
默认 allowDangerousHtml + 无条件 rehype-raw,页面文案(about intro、开源页
叙述、README)的 raw HTML 直通 set:html——script/style/iframe/on* 全不剥。

- 新增 src/lib/rehype-content-sanitize.ts:标签白名单(不含 script/style/
  iframe/object/form/link/meta/svg 等,危险标签整棵丢弃、未知标签解包保内容)
  + 属性白名单(a[href,title,rel]、img[src,alt,width,height,loading]、class/id)
  + 协议白名单(http/https/mailto + 站内相对 + 小图 data:)
- site-content.ts 插件链与文章管线同序:rehypeRaw → 内容净化 → class 白名单
  → 链接卡片(副作用为正:页面文案里的独立链接也会成 OG 卡片)
- 修两处自身实现坑(实测驱动):hast 的 class 属性名是 className(按 class
  过滤会把合法排版类一起剃掉);属性白名单需统一小写比较(驼峰 key 比不到)

验证: 注入载荷实测(<script>/<iframe>/onclick/style="color:red"/
      href="javascript:")全部剥离、合法 class="ink-2" 与文本保留;
      构建 48 页 exit 0;E2E 37/37

Spec: none (安全修复,方案 P0)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-02 16:57:52 +08:00
1 parent 859cac19a2
commit b0a3881725
2 files changed
+108 -1

No files matched your search

+15 -1
View File
@@ -121,7 +121,21 @@ export async function siteFavicon(): Promise<{ href: string; type: string }> {
let mdProcessor: Promise<import('@astrojs/markdown-remark').MarkdownProcessor> | null = null;
export async function renderMarkdown(md: string): Promise<string> {
const { createMarkdownProcessor } = await import('@astrojs/markdown-remark');
mdProcessor ??= createMarkdownProcessor();
// 与 astro.config.mjs 的文章管线同序:raw → 内容净化(标签/属性/协议)→
// class 白名单 → 链接卡片。此前这里 createMarkdownProcessor() 无参,页面
// 文案的 raw HTML 直通 set:html(方案 10 §7.1 实锤漏洞:script/style/on* 不剥)。
mdProcessor ??= (async () => {
const [{ default: rehypeRaw }, { default: rehypeContentSanitize }, { default: rehypeClassWhitelist }, { default: rehypeLinkCards }] =
await Promise.all([
import('rehype-raw'),
import('./rehype-content-sanitize'),
import('./rehype-class-whitelist'),
import('./rehype-link-cards'),
]);
return createMarkdownProcessor({
rehypePlugins: [rehypeRaw, rehypeContentSanitize, rehypeClassWhitelist, rehypeLinkCards],
});
})();
const { code } = await (await mdProcessor).render(md);
return code;
}