fix(sanitizer): 允许 KaTeX 渲染所需的 svg 与 path 标签及绘图属性
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled

KaTeX 在 SSR 渲染根号(\sqrt)、矩阵竖线/括号(\begin{pmatrix})、大括号、上/下括号、箭头等复杂公式时,通过 <svg> 和 <path> 绘制线条与图标。

原先 HTML 消毒器 (sanitizer) 的标签白名单中未包含 svg / path,导致 lol_html 过滤正文与评论 HTML 时将 <svg> 和 <path> 剥离,致使根号顶线与矩阵括号丢失。

改动:
1. DEFAULT_ALLOWED_TAGS 增加 svg / path 标签放行
2. Sanitizer 属性白名单放行 svg (xmlns, width, height, viewbox, preserveaspectratio, style) 与 path (d) 绘图属性
3. 补充 sanitizer 与 markdown 单元测试防御回归
This commit is contained in:
xfy 2026-07-23 13:27:13 +08:00
parent bcd13958ac
commit 6276bab6c6
2 changed files with 66 additions and 1 deletions

View File

@ -1321,6 +1321,22 @@ console.log(1)
result.html
);
}
#[test]
fn render_markdown_sqrt_and_matrix_preserves_svg() {
// \sqrt 与 \begin{pmatrix} 等 LaTeX 渲染需依赖 KaTeX 产生的 SVG 根号线与矩阵括号/竖线,
// 验证经过 sanitizer clean_html 后 <svg> 与 <path> 不会被误丢。
let result = render_markdown_enhanced("$$\\sqrt{\\pi} + \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}$$");
assert!(
result.html.contains("<svg"),
"KaTeX 根号/矩阵渲染的 <svg> 应保留, got: {}",
result.html
);
assert!(
result.html.contains("<path"),
"KaTeX 根号/矩阵渲染的 <path> 应保留, got: {}",
result.html
);
}
#[test]
fn render_markdown_inline_math_in_heading() {

View File

@ -82,6 +82,10 @@ static DEFAULT_ALLOWED_TAGS: LazyLock<HashSet<&'static str>> = LazyLock::new(||
"sub",
"summary",
"sup",
// svg / path 仅用于 KaTeX 服务端数学公式渲染 (根号、矩阵竖线、大括号、矢量箭头等);
// 属性仅放行 ViewBox / d 等绘图属性script/style 标签由 CLEAN_CONTENT_TAGS 强行清除。
"svg",
"path",
"table",
"tbody",
"td",
@ -388,6 +392,19 @@ pub fn clean_html(input: &str) -> String {
],
),
("span", vec!["class", "style"]),
// KaTeX 数学公式 SSR 渲染生成的 SVG 矢量图 (根号 / 矩阵竖线 / 括号 / 箭头等) 必备属性
(
"svg",
vec![
"xmlns",
"width",
"height",
"viewbox",
"preserveaspectratio",
"style",
],
),
("path", vec!["d"]),
("h1", vec!["id", "class"]),
("h2", vec!["id", "class"]),
("h3", vec!["id", "class"]),
@ -421,6 +438,19 @@ pub fn clean_comment_html(input: &str) -> String {
// span 的 styleKaTeX 服务端渲染产出的内联 style元素垂直对齐/定位)
// 需保留否则公式排版错位。与文章正文路径sanitizer.rs:382对齐。
("span", vec!["class", "style"]),
// KaTeX 数学公式 SSR 渲染生成的 SVG 矢量图 (根号 / 矩阵竖线 / 括号 / 箭头等) 必备属性
(
"svg",
vec![
"xmlns",
"width",
"height",
"viewbox",
"preserveaspectratio",
"style",
],
),
("path", vec!["d"]),
],
allowed_schemes: &DEFAULT_ALLOWED_SCHEMES,
allow_data_uri: false,
@ -503,6 +533,19 @@ mod tests {
"sumbody"
);
}
#[test]
fn katex_svg_and_path_preserved() {
let katex_html = crate::api::katex::render_display("\\sqrt{\\pi}");
let cleaned = clean_html(&katex_html);
assert!(cleaned.contains("<svg"), "clean_html should preserve <svg> for KaTeX sqrt");
assert!(cleaned.contains("<path"), "clean_html should preserve <path> for KaTeX sqrt");
assert!(cleaned.contains("d="), "clean_html should preserve d attribute on <path>");
assert!(cleaned.contains("viewBox=") || cleaned.contains("viewbox="), "clean_html should preserve viewBox attribute on <svg>");
let comment_cleaned = clean_comment_html(&katex_html);
assert!(comment_cleaned.contains("<svg"), "clean_comment_html should preserve <svg>");
assert!(comment_cleaned.contains("<path"), "clean_comment_html should preserve <path>");
}
#[test]
fn comment_removes_data_uris() {
@ -785,7 +828,7 @@ mod tests {
#[test]
fn clean_html_drops_dangerous_tags_entirely() {
// 这些标签不在白名单:整体移除(含子树),无法用于 XSS / 数据外泄。
for tag in ["iframe", "object", "embed", "form", "svg", "math", "base", "meta"] {
for tag in ["iframe", "object", "embed", "form", "math", "base", "meta"] {
let input = format!("<{tag} src=\"javascript:alert(1)\"></{tag}>");
let result = clean_html(&input);
assert!(
@ -793,6 +836,12 @@ mod tests {
"<{tag}> 不在白名单应被移除: {result}"
);
}
// svg 在白名单中(配合 KaTeX 渲染),但非白名单属性(如 src / onerror / onload会被剥离
let svg_input = r#"<svg src="javascript:alert(1)" onload="alert(2)"></svg>"#;
let svg_result = clean_html(svg_input);
assert!(!svg_result.contains("javascript"), "svg 上的非白名单属性/javascript 应被剥离: {svg_result}");
assert!(!svg_result.contains("onload"), "svg 上的 onload 事件处理器应被剥离: {svg_result}");
}
#[test]