news 2026/9/7 14:17:45

Mermaid 配置净化机制解析:sanitize() 如何拦截恶意指令对安全配置键的覆盖

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mermaid 配置净化机制解析:sanitize() 如何拦截恶意指令对安全配置键的覆盖

Mermaid 配置净化机制解析:sanitize() 如何拦截恶意指令对安全配置键的覆盖

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

Mermaid 支持通过%%{init}%%指令在图内直接修改渲染配置,这给渲染用户生成内容的应用程序带来了风险:恶意构造的图可能试图覆盖安全策略或注入脚本。本文以官方 API 文档 sanitize.md 为主体,结合 packages/mermaid/src/config.ts 的真实实现、packages/mermaid/src/utils/sanitizeDirective.ts 的配套防线与单元测试,讲清楚sanitize()函数的职责、四条拦截规则、它在配置链路中的触发位置,以及secure配置项如何配合mermaid.initialize实现安全键的"只读保护"。读完本文,你可以理解 Mermaid 如何防御配置层面的原型污染与 XSS,并知道在集成 Mermaid 时应把哪些关键配置锁定为 secure。

1. 官方 API 文档对 sanitize() 的定义

自动生成的 API 文档 docs/config/setup/config/functions/sanitize.md 给出的函数签名为:

sanitize(options): void
  • 定义位置:packages/mermaid/src/config.ts
  • 功能:确保 options 参数不会试图覆盖siteConfig中的 secure(安全)键。
  • 参数options,类型为any,即"潜在的setConfig参数"。
  • 返回值void
  • 重要备注(Remarks)会原地(in-place)修改 options 对象——被判定为违规的键会被直接从传入的对象上delete掉,而不是返回一份过滤后的副本。

这个备注对集成方很关键:任何把原始配置对象缓存起来、期望其内容不变的调用方,都会在sanitize()执行后发现部分键"消失"了。

2. sanitize() 的实现逐条解析

sanitize()的完整实现位于 packages/mermaid/src/config.ts:

export const sanitize = (options: any) => { if (!options) { return; } // Checking that options are not in the list of excluded options ['secure', ...(siteConfig.secure ?? [])].forEach((key) => { if (Object.hasOwn(options, key)) { // DO NOT attempt to print options[key] within `${}` as a malicious script // can exploit the logger's attempt to stringify the value and execute arbitrary code log.debug(`Denied attempt to modify a secure key ${key}`, options[key]); delete options[key]; } }); // Check that there no attempts of prototype pollution Object.keys(options).forEach((key) => { if (key.startsWith('__')) { delete options[key]; } }); // Check that there no attempts of xss, there should be no tags at all in the directive // blocking data urls as base64 urls can contain svg's with inline script tags Object.keys(options).forEach((key) => { if ( typeof options[key] === 'string' && (options[key].includes('<') || options[key].includes('>') || options[key].includes('url(data:')) ) { delete options[key]; } if (typeof options[key] === 'object') { sanitize(options[key]); } }); };

可以拆解为四条规则:

  1. 空值守卫optionsnull/undefined时直接返回,不做任何处理。
  2. secure 键保护:遍历['secure', ...(siteConfig.secure ?? [])],只要传入对象拥有(Object.hasOwn精确匹配自有属性)其中任何一个键,就删除该键并打一条 debug 日志。注意两点:
    • secure键本身永远在保护名单中——图内指令永远不能修改"哪些键是受保护的",形成自我防御闭环;
    • 源码中的注释特别说明:日志中不要用模板字符串插值options[key],因为恶意脚本可以构造特殊值,利用 logger 对值做字符串化时触发任意代码执行。这是典型的"日志也是攻击面"的防御细节。
  3. 原型污染防御:删除所有以__开头的键(即__proto__一类),阻止通过配置合并污染原型链。
  4. XSS 字符串过滤 + 递归:任何字符串值若包含<>url(data:即被整体删除——指令中不允许出现任何标签,而data:URL 被拦截是因为 base64 编码的 data URL 里可以藏带内联脚本的 SVG。最后对值为对象属性的键递归调用自身,使上述规则对嵌套配置(如flowchart: {...})同样生效。

3. secure 配置项:哪些键受保护

secure是一个字符串数组类型的顶层配置项,其类型定义见 packages/mermaid/src/config.type.ts:

/** * This option controls which `currentConfig` keys are considered secure and * can only be changed via call to `mermaid.initialize`. * This prevents malicious graph directives from overriding a site's default security. */ secure?: string[];

注释阐明了设计意图:secure 键只能通过mermaid.initialize修改,防止恶意的图内指令(directive)覆盖站点默认的 security 策略

单元测试 packages/mermaid/src/config.spec.ts 中 "should respect secure keys when applying directives" 用例完整验证了这条链路:

const config_0: MermaidConfig = { fontFamily: 'foo-font', securityLevel: 'strict', // can't be changed fontSize: 12345, // can't be changed secure: [...configApi.defaultConfig.secure!, 'fontSize'], }; configApi.setSiteConfig(config_0); const directive: MermaidConfig = { fontFamily: 'baf', fontSize: 54321, securityLevel: 'loose', }; configApi.addDirective(directive); const cfg: MermaidConfig = configApi.getConfig(); expect(cfg.fontFamily).toEqual(directive.fontFamily); expect(cfg.fontSize).toBe(config_0.fontSize); expect(cfg.securityLevel).toBe(config_0.securityLevel);

该用例说明:defaultConfig自带一组默认 secure 键(测试中通过defaultConfig.secure!展开),站点可以再追加fontSize等自定义安全键;随后图内指令虽然同时携带了fontFamily(可改)、fontSize(secure)与securityLevel(secure),但最终只有fontFamily生效。

secure 机制在渲染逻辑中也有实际体现。例如流程图数据库 packages/mermaid/src/diagrams/flowchart/flowDb.ts 在边数超过maxEdges上限时抛出的错误信息明确写道:

Initialize mermaid with maxEdges set to a higher number to allow more edges. You cannot set this config via configuration inside the diagram as it is a secure config. You have to call mermaid.initialize.

这正是secure名单把maxEdges类防御性阈值锁死的直接结果——防止恶意图通过指令调高限制来放大资源消耗。

4. 调用链路:sanitize() 在何处被触发

从源码结构看,sanitize()是配置合并流水线updateCurrentConfig的前置过滤器(packages/mermaid/src/config.ts):

const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => { let cfg: MermaidConfig = assignWithDepth({}, siteCfg); let sumOfDirectives: MermaidConfig = {}; for (const d of _directives) { sanitize(d); // ← 每条指令先净化,再深度合并 sumOfDirectives = assignWithDepth(sumOfDirectives, d); } cfg = assignWithDepth(cfg, sumOfDirectives); // ...theme 处理... currentConfig = cfg; checkConfig(currentConfig); return currentConfig; };

由此得到三条主要触发路径:

入口路径说明
图内指令addDirective()updateCurrentConfig(siteConfig, directives)sanitize(d)每条%%{init}%%指令在参与合并前逐条净化;addDirective()内部还会先调用姊妹函数sanitizeDirective()做白名单校验(见第 5 节)
编程式配置setConfig(conf)updateCurrentConfig(currentConfig, [conf])sanitize(conf)见 config.ts,setConfig被 packages/mermaid/src/mermaidAPI.ts 以setConfig: configApi.setConfig的形式暴露到 mermaid API 上
重置reset(config?)updateCurrentConfig(config, [])指令清空后以 siteConfig 重建 currentConfig

需要区分的是:setSiteConfig()/updateSiteConfig()不走sanitize()。这是有意的设计——sanitize()保护的是"从指令/setConfig进入 currentConfig 的数据",而 siteConfig 属于站点侧受信任的输入,只能通过应用自身的mermaid.initialize调用建立。

5. 第二道防线:sanitizeDirective() 的白名单校验

sanitize()之外,packages/mermaid/src/utils/sanitizeDirective.ts 中的sanitizeDirective()对指令 JSON 做更严格的清洗,由addDirective()sanitize之前调用。两者分工互补:

  • 键级白名单:删除__前缀键、键名中含proto/constr的键、不在configKeys(源自 packages/mermaid/src/defaultConfig.js 派生的合法键集合)中的键,以及值为null的键。相比sanitize()只拦__前缀,这里进一步把任何拼上未知键的尝试都丢弃。
  • 字典型配置的取值校验:像 sankey 的nodeColors、treeView 的filenameIcons/extensionIcons这类"键由用户任意定义"的配置不查键名,而是对取值做模式匹配(如nodeColors必须是#hex/rgb(...)/hsl(...)/ 命名色),可疑条目直接删除(sanitizeDirective.ts)。
  • CSS 相关键的括号平衡检查themeCSSfontFamilyaltFontFamily等键经sanitizeCss()校验,大括号不平衡时整体替换为{ /* ERROR: Unbalanced CSS */ }占位,防止截断的 CSS 逃逸出预期作用域。
  • themeVariables 字符白名单:值不匹配/^[\d "#%(),.;A-Za-z]+$/时被置空,杜绝样式变量里夹带特殊字符。

仓库的 e2e 目录还包含xssghsa系列测试页面(如 e2e/other/xss.spec.js),从测试组织上可见团队对"指令注入 → 渲染输出"这条攻击路径的持续回归验证;配合 docs/community/security.md 中关于 DOMPurify 默认基线配置与漏洞上报流程的说明,sanitize()/sanitizeDirective()属于"配置层"防御,DOMPurify 属于"输出层"防御,二者共同构成 XSS 缓解体系。

6. 集成实践建议

  1. 把安全策略写入mermaid.initializesecurityLevelstrict/loose/antiscript/sandbox,见 config.type.ts)以及maxEdges等防御性阈值,应通过initialize设定并列入secure数组,而不是依赖图内指令。
  2. 利用默认 secure 名单再追加defaultConfig已内置一组 secure 键(测试用例中以defaultConfig.secure!展开追加即为官方推荐写法),站点侧只需补充自己关心的键。
  3. 注意原地修改语义sanitize()会直接delete调用方传入对象的键,若在业务代码中复用同一配置对象,净化后再使用,不要假设对象内容不变。
  4. 不要绕过setSiteConfig不受sanitize约束,意味着应用代码传入initialize的内容是受信任的;若这部分内容来自用户输入,应用需自行先做校验。

7. 延伸阅读:本仓库的相关文档与源码

  • API 文档(本文主体):docs/config/setup/config/functions/sanitize.md,同目录下还有 setConfig.md、addDirective.md、reset.md 等配套函数文档
  • 实现源码:packages/mermaid/src/config.ts(sanitize/updateCurrentConfig)、packages/mermaid/src/utils/sanitizeDirective.ts
  • 类型定义:packages/mermaid/src/config.type.ts(secure键)
  • 行为验证:packages/mermaid/src/config.spec.ts、packages/mermaid/src/config.usecase.spec.ts
  • 配置层使用约束示例:packages/mermaid/src/diagrams/flowchart/flowDb.ts
  • 安全总览:docs/community/security.md

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 14:17:33

腾讯开源多模态本地搜索工具:让图片视频文本统一检索

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:15:45

ARM Mali GPU开发:libmali链接与动态库加载排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:13:02

维普重点标红文献综述和理论分析的降AI修改方法

维普重点标红文献综述和理论分析的降AI修改方法 在公共管理与城市空间治理现代化政策评估方向的硕士学位论文维普审查中&#xff0c;综述与理论部分的连续高亮让很多同学倍感焦虑&#xff1a;维普重点标红文献综述和理论分析的降AI修改方法该怎么做&#xff1f;整篇 3.3 万字的…

作者头像 李华
网站建设 2026/9/7 14:06:46

单核处理器开发板线程冲突全解析:从抢占式调度到互斥锁实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华