fix(render): escape URLs in links and images to prevent XSS
Apply html_escape to: - Link href and title attributes (start_tag_to_html) - Image src attribute (Event::End TagEnd::Image handler) Add test cases for: - Quote-breaking URL attacks - Link title escaping - Image src escaping Addresses HIGH severity finding from security audit. All 70 tests pass.
This commit is contained in:
@@ -149,13 +149,13 @@ pub fn markdown_to_html(markdown: &str) -> (String, Vec<Anchor>) {
|
||||
if title.is_empty() {
|
||||
html_output.push_str(&format!(
|
||||
"<img src=\"{}\" alt=\"{}\" />",
|
||||
src,
|
||||
html_escape(&src),
|
||||
html_escape(&alt)
|
||||
));
|
||||
} else {
|
||||
html_output.push_str(&format!(
|
||||
"<img src=\"{}\" alt=\"{}\" title=\"{}\" />",
|
||||
src,
|
||||
html_escape(&src),
|
||||
html_escape(&alt),
|
||||
html_escape(&title)
|
||||
));
|
||||
@@ -283,9 +283,13 @@ fn start_tag_to_html(tag: &Tag) -> String {
|
||||
dest_url, title, ..
|
||||
} => {
|
||||
if title.is_empty() {
|
||||
format!("<a href=\"{}\">", dest_url)
|
||||
format!("<a href=\"{}\">", html_escape(&dest_url))
|
||||
} else {
|
||||
format!("<a href=\"{}\" title=\"{}\">", dest_url, title)
|
||||
format!(
|
||||
"<a href=\"{}\" title=\"{}\">",
|
||||
html_escape(&dest_url),
|
||||
html_escape(&title)
|
||||
)
|
||||
}
|
||||
}
|
||||
Tag::Image { .. } => String::new(), // Handled separately in main loop
|
||||
@@ -459,4 +463,37 @@ Config details.
|
||||
// Consecutive special chars → single hyphen
|
||||
assert_eq!(slugify("A -- B"), "a-b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_url_escaping() {
|
||||
// Quote-breaking attack
|
||||
let md = r#"[click]("><script>alert(1)</script>)"#;
|
||||
let (html, _) = markdown_to_html(md);
|
||||
assert!(!html.contains("<script>"), "script tags should be escaped");
|
||||
assert!(html.contains(">"), "angle brackets should be escaped");
|
||||
|
||||
// JavaScript URL (should be escaped, not executed)
|
||||
let md = r#"[click](javascript:alert(1))"#;
|
||||
let (html, _) = markdown_to_html(md);
|
||||
assert!(html.contains("href=\"javascript:alert(1)\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_title_escaping() {
|
||||
let md = r#"[text](url "title with \"quotes\"")"#;
|
||||
let (html, _) = markdown_to_html(md);
|
||||
assert!(html.contains("""), "quotes in title should be escaped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_image_src_escaping() {
|
||||
// Quote-breaking attack in image src
|
||||
let md = r#"</script>)"#;
|
||||
let (html, _) = markdown_to_html(md);
|
||||
assert!(!html.contains("<script>"), "script tags should be escaped");
|
||||
assert!(
|
||||
html.contains(""") || html.contains(">"),
|
||||
"special chars in src should be escaped"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user