From 8d22bceac9933c252db8461c571f17f097dcfc86 Mon Sep 17 00:00:00 2001 From: nichtLehdev Date: Fri, 18 Sep 2026 10:32:07 +0200 Subject: [PATCH] fix(sicherheit): Klartext-Helfer laesst garantiert keine Tags stehen CodeQL meldete am Release-PR vier Warnungen hoher Stufe im Klartext- Helfer aus #311: - Tags wurden in einem Durchgang entfernt. Aus <script> macht das ipt>alert(1)"); + ohneSkript("</script>"); + }); + + it("lässt aus Entitäten keine Tags entstehen", () => { + ohneSkript("<script>alert(1)</script>"); + expect(markdownToPlainText("<b>fett</b>")).toBe("fett"); + }); + + it("entschlüsselt Entitäten nur einmal", () => { + expect(markdownToPlainText("&lt;")).toBe("<"); + expect(markdownToPlainText("Bläser & Chor")).toBe("Bläser & Chor"); + }); + + it("lässt spitze Klammern im Text stehen", () => { + expect(markdownToPlainText("Kinder <10 Jahre")).toBe("Kinder <10 Jahre"); + expect(markdownToPlainText("a < b und c > d")).toBe("a < b und c > d"); + }); +}); diff --git a/src/lib/markdown-to-plain-text.ts b/src/lib/markdown-to-plain-text.ts index a34b69a0..1ffe20a3 100644 --- a/src/lib/markdown-to-plain-text.ts +++ b/src/lib/markdown-to-plain-text.ts @@ -22,6 +22,33 @@ const ENTITIES: Record = { */ const MASKIERT_AB = 0xe000; +/** + * Wendet eine Ersetzung an, bis sich nichts mehr ändert. Ein einzelner + * Durchgang reicht beim Entfernen von Tags nicht: Aus `<script>` macht er + * `` das „alert(1)" im // Kalendereintrag stehen — gemessen. Gefährlich ist das nicht (Klartext // wird nirgends ausgeführt), nur falsch. - text = text.replace(/<(script|style)\b[\s\S]*?<\/\1\s*>/gi, ""); + text = bisStabil(text, SKRIPT_ODER_STIL); - // Sonst nur echte Tags entfernen, nicht jedes spitze Klammerpaar: „Kinder - // <10 Jahre" ist Text und wird in der Anzeige auch als Text dargestellt. - // Die frühere Fassung löschte pauschal `<` und `>` und machte aus - // `Wort` das Wort „uWort/u". - text = text.replace(/<\/?[a-zA-Z][^<>]*>/g, ""); + // Sonst nur echte Tags entfernen (siehe `TAG`). Die frühere Fassung + // löschte pauschal `<` und `>` und machte aus `Wort` „uWort/u". + text = bisStabil(text, TAG); // Das Ziel darf ein Klammerpaar enthalten (`javascript:alert(1)`, aber auch // Wikipedia-Adressen mit Klammern) — eine Ebene reicht dafür. Vorher blieb @@ -103,9 +128,14 @@ export function markdownToPlainText(markdown: string): string { String.fromCharCode(zeichen.charCodeAt(0) - MASKIERT_AB), ); - for (const [entity, char] of Object.entries(ENTITIES)) { - text = text.split(entity).join(char); - } + // In einem einzigen Durchgang: Nacheinander aufgelöst würde aus „&lt;" + // erst „<" und dann „<" — doppelt entschlüsselt. + text = text.replace(ENTITY, (entity) => ENTITIES[entity] ?? entity); + + // Aufgelöste Entitäten können Tags ergeben („<script>" → „