From 6638696deae3a4a469a3b82a6bb706e4fe875e78 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Thu, 5 Feb 2026 17:10:48 -0700 Subject: [PATCH] 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. --- src/render.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/render.rs b/src/render.rs index c14920b..38bb835 100644 --- a/src/render.rs +++ b/src/render.rs @@ -149,13 +149,13 @@ pub fn markdown_to_html(markdown: &str) -> (String, Vec) { if title.is_empty() { html_output.push_str(&format!( "\"{}\"", - src, + html_escape(&src), html_escape(&alt) )); } else { html_output.push_str(&format!( "\"{}\"", - 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!("", dest_url) + format!("", html_escape(&dest_url)) } else { - format!("", dest_url, title) + format!( + "", + 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](">)"#; + let (html, _) = markdown_to_html(md); + assert!(!html.contains(")"#; + let (html, _) = markdown_to_html(md); + assert!(!html.contains("