diff --git a/src/api/posts.rs b/src/api/posts.rs
index 693f1ca..2458895 100644
--- a/src/api/posts.rs
+++ b/src/api/posts.rs
@@ -166,6 +166,17 @@ async fn ensure_unique_slug(
// Markdown rendering (enhanced with TOC, word count, reading time, anchors)
// ============================================================================
+#[cfg(feature = "server")]
+fn clean_html(input: &str) -> String {
+ let mut builder = ammonia::Builder::default();
+ builder
+ .add_generic_attributes(&["class", "aria-hidden", "aria-label", "id", "role", "accesskey", "title"])
+ .add_tags(&["details", "summary"])
+ .url_relative(ammonia::UrlRelative::PassThrough)
+ .clean(input)
+ .to_string()
+}
+
#[derive(Debug, Clone)]
#[cfg(feature = "server")]
struct RenderedContent {
@@ -272,10 +283,10 @@ fn render_markdown_enhanced(md: &str) -> RenderedContent {
if in_heading {
// Manually render heading content
match event {
- Event::Text(text) => html.push_str(&ammonia::clean(&text)),
+ Event::Text(text) => html.push_str(&clean_html(&text)),
Event::Code(code) => {
html.push_str("");
- html.push_str(&ammonia::clean(&code));
+ html.push_str(&clean_html(&code));
html.push_str("");
}
_ => {}
@@ -297,7 +308,7 @@ fn render_markdown_enhanced(md: &str) -> RenderedContent {
let reading_time = (word_count / 200).max(1);
RenderedContent {
- html: ammonia::clean(&html),
+ html: clean_html(&html),
toc_html,
word_count,
reading_time,
@@ -343,8 +354,8 @@ fn generate_toc_html(headings: &[(u8, String, String)]) -> String {
html.push_str(&format!(
"