Markdown 不是天然安全的纯文本
Markdown 最终会转换成 HTML。若允许原始 HTML、危险链接或不受控图表脚本,编辑者粘贴的一段内容可能在读者浏览器执行代码、发起外部请求或伪造页面元素。即使站点只有一个管理员,也要考虑账号误用、复制不可信内容和未来多人编辑。
CTY Log 的公开实现默认禁用原始 HTML,并对生成结果做白名单清洗。本文只讨论内容渲染链路;认证、上传存储和服务器安全仍需独立控制。
先确定允许的语法
普通段落、标题、列表、表格、引用、受限链接、代码块、数学公式和 Mermaid 已能覆盖大部分技术写作。没有明确需求时不开放任意 iframe、事件属性、内联脚本或自定义 HTML。功能列表越小,审查和测试越明确。
清洗必须发生在可信渲染边界,而不是依赖编辑器预览。浏览器端预览和服务端正式输出应使用相同规则,否则“预览正常”不能证明发布结果安全。
链接和图片
链接协议只允许明确集合,例如站内相对路径、https 和必要时的 mailto。必须拒绝 javascript:、危险 data: 和经过大小写或编码混淆的变体。外部链接如果新窗口打开,需要同步设置 noopener noreferrer。
图片来源应限制为站内媒体接口或配置允许的 HTTPS 地址。上传服务验证扩展名、MIME、文件头、大小和尺寸,使用随机存储名;Markdown 本身不能绕过上传接口引用服务器任意文件。替代文本既是可访问性信息,也不能被拼接为未经转义的属性。
代码、公式与图表
代码高亮器应把代码当文本,不执行其中的 HTML。语言名要映射到支持列表,未知语言回退为纯文本。公式渲染同样需要禁用能访问外部资源或注入 HTML 的扩展。
Mermaid 接受的是图表语言,不应把它视为普通图片。渲染器使用 strict 安全模式,禁止任意 HTML 标签、点击脚本和外部回调。图表失败时显示清晰错误或原始代码,不应通过放宽安全级别“修好”内容。
必须有的回归测试
测试样本至少包括脚本标签、事件属性、危险协议链接、编码混淆链接、图片错误处理、代码块中的 HTML、公式扩展和 Mermaid 点击指令。断言最终 HTML 中不存在脚本、事件属性和危险 URL,同时正常标题、表格、代码和公式仍能渲染。
内容安全策略是第二道防线,不应替代清洗。当前框架若需要内联脚本,应记录这个边界,并在支持稳定 nonce 后继续收紧;图片和连接目标也根据真实依赖配置,而不是使用无限通配。
保密与 XSS 是两件事
内容通过 XSS 检查并不代表可以公开。内部名称、真实报文、地址、日志和截图仍需发布前人工审查。技术清洗解决“内容会不会执行”,保密检查解决“内容应不应该出现”。
结论
安全的 Markdown 发布链路从最小语法集合开始,经过协议校验、插件限制和最终 HTML 白名单清洗,再由 CSP 提供纵深防护。任何新增渲染能力都应同时增加恶意样本和正常样本测试,而不是只验证展示效果。