How to ensure rendered markdown is safe? (pulldown_cmark)

⚓ rust    📅 2025-05-22    👤 surdeus    👁️ 3      

surdeus

Warning

This post was published 38 days ago. The information described in this article may have changed.

Hello, am trying to create a WASM markdown renderer with syntax highlighting and so far it has been successful. However, I would like to know if there is a good way to ensure that the rendered HTML is safe for browser use (no XSS etc). Here is my current code

use std::sync::LazyLock;

use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
use syntect::highlighting::ThemeSet;
use syntect::html::highlighted_html_for_string;
use syntect::parsing::SyntaxSet;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn render_md(markdown: &str, theme: &str) -> String {
    static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);
    static THEME_SET: LazyLock<ThemeSet> = LazyLock::new(|| ThemeSet::load_defaults());

    let theme = THEME_SET.themes[theme].clone();

    let mut sr = SYNTAX_SET.find_syntax_plain_text();
    let mut code = String::new();
    let mut html_output = String::new();
    let mut code_block = false;

    let parser = Parser::new_ext(markdown, Options::all()).filter_map(|event| match event {
        Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))) => {
            let lang = lang.trim();
            sr = SYNTAX_SET
                .find_syntax_by_token(&lang)
                .unwrap_or_else(|| SYNTAX_SET.find_syntax_plain_text());
            code_block = true;
            None
        }
        Event::End(TagEnd::CodeBlock) => {
            let html = highlighted_html_for_string(&code, &SYNTAX_SET, &sr, &theme)
                .unwrap_or(code.clone());

            code.clear();
            code_block = false;
            Some(Event::Html(html.into()))
        }

        Event::Text(t) => {
            if code_block {
                code.push_str(&t);
                return None;
            }
            Some(Event::Text(t))
        }
        _ => Some(event),
    });

    pulldown_cmark::html::push_html(&mut html_output, parser);

    html_output
}

For example, for input

[Malicious](javascript:alert('XSS'))

I am able to click on the link and trigger the alert.

Thanks in advance!

2 posts - 2 participants

Read full topic

🏷️ rust_feed