From 36295fcd6a65ae01f2a28ae7e16a8486795f5e40 Mon Sep 17 00:00:00 2001 From: ghassan Date: Wed, 21 Oct 2026 10:21:17 +0300 Subject: [PATCH] fix(description): render leaked markdown headers instead of showing raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI-generated descriptions ("Letra" / "Chorus" / "Translation") landed in the DB with markdown prefixes that the sanitiser treated as plain text — so viewers saw literal "## Letra" and "### Chorus" instead of headings. HtmlSanitizer::render() now promotes `##` / `###` at the start of a line into

/

before sanitising, so the existing allowlist accepts them and the reader sees real headings. Only touches lines whose entire content is a heading — markdown mid-sentence is left alone to avoid false positives. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/Support/HtmlSanitizer.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/Support/HtmlSanitizer.php b/app/Support/HtmlSanitizer.php index e331d77..b400cb8 100644 --- a/app/Support/HtmlSanitizer.php +++ b/app/Support/HtmlSanitizer.php @@ -77,6 +77,11 @@ class HtmlSanitizer /** * Produce safe display HTML. Legacy plain-text values (no tags) are * escaped and converted to
; rich values are run through clean(). + * + * Also detects a handful of common markdown-ish prefixes that leaked + * in through AI-generated descriptions ("## Letra", "### Chorus") and + * turns them into real

/

tags before sanitising. Without this + * step a reader saw literal "## Letra" instead of a heading. */ public static function render(?string $value): string { @@ -85,6 +90,8 @@ class HtmlSanitizer return ''; } + $value = self::promoteLeakedMarkdown($value); + if (strip_tags($value) === $value) { return nl2br(e($value), false); } @@ -92,6 +99,24 @@ class HtmlSanitizer return self::clean($value); } + /** + * Convert markdown-style heading prefixes at the start of a line into + * real HTML tags, so the sanitiser can keep them. Only touches lines + * that start with ##, ###, or **…**; everything else is untouched. + */ + private static function promoteLeakedMarkdown(string $value): string + { + $lines = preg_split("/\r?\n/", $value); + foreach ($lines as $i => $line) { + if (preg_match('/^\s*###\s+(.+?)\s*$/u', $line, $m)) { + $lines[$i] = '

' . e($m[1]) . '

'; + } elseif (preg_match('/^\s*##\s+(.+?)\s*$/u', $line, $m)) { + $lines[$i] = '

' . e($m[1]) . '

'; + } + } + return implode("\n", $lines); + } + private static function sanitizeChildren(DOMNode $node): void { foreach (iterator_to_array($node->childNodes) as $child) {