Insights / Technische Beiträge
Markdown-Links in KI-Chat-Antworten sicher rendern
Eine technische Notiz dazu, Markdown-Links aus KI-Chat-Antworten sicher in HTML zu überführen. Parsing mit Leerzeichen-Toleranz, href-trim, Allowlist, DOM-Rendering, Fallback und Tests werden getrennt betrachtet.
Inhaltsverzeichnis
Wenn ein KI-Chat Siehe [Services]( /services/ ) ausgibt, kann der Link nicht gerendert werden und das rohe Markdown bleibt sichtbar.
Acecore traf diesen Fall im Kontakt-KI-Chat und passte den Renderer in dem PR zur Korrektur des Markdown-Link-Renderings an.
Dieser Artikel nutzt diese kleine Korrektur als Einstieg, um KI-Antworten sicher in DOM umzuwandeln.
KI-Antworten sind kein vertrauenswürdiges HTML
Modellausgaben sollten als Text behandelt werden.
Links, Fettschrift und Listen sind in einem Chat nützlich. Aber innerHTML lässt den Browser beliebige Modellstrings interpretieren.
Es braucht keine komplette Markdown-Implementierung. Ein kleiner Renderer, der nur unterstützte Ausdrücke erkennt und sichere DOM-Knoten erzeugt, ist oft besser.
Das Problem ist nicht nur Leerraum
Der konkrete Fehler war ein Link wie:
[Services](/services/)
Eine strenge Regex geht oft von einer URL ohne Leerzeichen aus:
/\[([^\]]+)\]\(([^)\s]+)\)/;
[^)\s]+ lehnt Leerzeichen ab. Deshalb wird ( /services/ ) nicht erkannt. Die Korrektur toleriert Leerraum in den Klammern und normalisiert danach.
/\[([^\]]+)\]\(\s*([^)]+?)\s*\)/;
Damit ist es aber nicht getan. Der normalisierte Wert muss validiert werden.
href vor der Validierung trimmen
Der Ablauf sollte fest sein:
- Label und raw href aus Markdown extrahieren
trim()auf raw href anwenden- Getrimmtes href per Allowlist validieren
<a>nur bei erlaubtem href erzeugen
const href = String(rawHref || "").trim();
if (label && isSafeMarkdownHref(href)) {
const link = document.createElement("a");
link.href = href;
link.rel = "noopener noreferrer";
if (/^https?:\/\//i.test(href)) {
link.target = "_blank";
}
link.textContent = label;
parent.appendChild(link);
}
Der validierte Wert muss derselbe sein, der ins DOM geschrieben wird.
Die Allowlist ist produktspezifisch
Jede Site muss entscheiden, welche URLs ihre KI anzeigen darf.
| Typ | Beispiel | Entscheidung |
|---|---|---|
| Interner Pfad | /services/ |
Erlauben |
| Gleiche Origin | https://acecore.net/... |
Erlauben |
| Offizielles LINE | https://lin.ee/... |
Als offizieller Kanal erlauben |
| mailto | mailto:info@acecore.net |
Nur feste Adresse |
| tel | tel:05088902788 |
Nur feste Nummer |
| Andere externe | Beliebige URL | Standardmäßig nicht verlinken |
function isSafeMarkdownHref(href) {
if (href.startsWith("/")) return true;
try {
const url = new URL(href, window.location.origin);
if (url.origin === window.location.origin) return true;
if (url.hostname === "acecore.net") return true;
if (url.hostname === "lin.ee") return true;
} catch {
return false;
}
return href === "mailto:info@acecore.net" || href === "tel:05088902788";
}
Eine Recruiting-Seite kann Jobportale erlauben, ein SaaS Dokumentation und Statusseite. Die Funktion muss zur Policy des Produkts passen.
Fallback auf Text
Wenn ein Link die Validierung nicht besteht, ist Löschen nicht immer die beste Wahl.
Im Kontaktchat erhält Text den Kontext für Nutzer und zeigt Entwicklern, was das Modell ausgeben wollte. Der Renderer muss sichere Links erzeugen und sicher fehlschlagen können.
Fehlerfälle testen
Mindestens diese Fälle sollten abgedeckt sein:
| Eingabe | Erwartung |
|---|---|
[Services](/services/) |
Interner Link |
[Services]( /services/ ) |
Interner Link nach trim |
[LINE]( https://lin.ee/example ) |
Erlaubter externer Link |
[Böse](javascript:alert(1)) |
Kein Link |
[Extern](https://example.com/) |
Kein Link, wenn Domain nicht erlaubt |
[Kaputt](/services/ |
Anzeige als Text |
Im PR #99 wurde bestätigt, dass Varianten mit und ohne Leerzeichen auf dieselbe erwartete URL führen.
Nicht ganz Markdown standardmäßig implementieren
Für Chat reicht meist:
- Absätze
- Listen
- Fett
- Inline-Code
- Links
Tabellen, Bilder, rohes HTML und Fußnoten vergrößern den Scope schnell. Selbst mit einer Bibliothek bleiben HTML- und URL-Policy eigene Entscheidungen.
Zusammenfassung
Markdown-Link-Rendering in KI-Antworten wirkt wie ein kleines UI-Detail, legt aber die Vertrauensgrenze für Modellausgaben fest.
Praktisch heißt das: erst Text, kleine Teilmenge, trim vor Validierung, strenge Allowlist und sicherer Fallback.
Rendering-Ablauf für Links
Text
Die Modellantwort zuerst als reinen Text behandeln.
Parse
Nur die Markdown-Ausdrücke erkennen, die der Chat wirklich nutzt.
Validate
href trimmen und nur interne URLs oder freigegebene Domains erlauben.
Render
Sichere Elemente mit der DOM API erzeugen, nicht mit innerHTML.
Entscheidungen, die getrennt bleiben sollten
Lockeres Rendering
- KI-Antworten direkt in innerHTML einsetzen
- Die gesamte Markdown-Spezifikation auf einmal implementieren
- Links mit Leerzeichen um die URL nicht erkennen
- Externe URLs und javascript: gleich behandeln
Kleines und sicheres Rendering
- Antworten als Text empfangen und nur Nötiges in DOM umwandeln
- Nur die im Chat genutzte Markdown-Teilmenge unterstützen
- URLs nach trim validieren
- Nicht erlaubte URLs als Text belassen
Implementierungscheck
- KI-Antworten nicht als HTML vertrauen
- Leerzeichen um Markdown-Link-URLs akzeptieren
- href immer vor der Validierung trimmen
- Nur interne Pfade, aktuelle Origin und nötige externe Domains erlauben
- target und rel für externe Links explizit setzen
- Abgelehnte Links als Text erhalten
- Gefährliche URLs und kaputtes Markdown testen
Zugehörige Seiten
Häufige Fragen
Reicht markdown-it oder marked?
Auch mit Bibliothek müssen HTML-Ausgabe, erlaubte Linkziele, target und rel sowie die Ablehnung gefährlicher URLs gestaltet werden. Für einen Chat kann ein kleiner eigener Renderer genügen.
Sind Leerzeichen um URLs gefährlich?
Nicht die Leerzeichen sind das Risiko, sondern was nach dem Trim erlaubt wird. Validierung des getrimmten href hält die Allowlist streng.
Sollte man abgelehnte URLs löschen?
Meist ist Text besser für Debugging und Kontext. Eine strengere Policy kann den gesamten Link entfernen.