Insights / Articles techniques

Rendre en sécurité les liens Markdown dans les réponses de chat IA

Note technique sur la conversion sécurisée des liens Markdown dans les réponses IA. En séparant parsing tolérant aux espaces, trim de href, allowlist, rendu DOM, fallback et tests, le même modèle devient réutilisable.

  • Technologie
  • Site web
  • AI
  • Sécurité
  • Astro
Rendre en sécurité les liens Markdown dans les réponses de chat IA
Sommaire
  1. Les réponses IA ne sont pas du HTML fiable
  2. Le problème ne se limite pas aux espaces
  3. Trimmer href avant validation
  4. La allowlist dépend du produit
  5. Revenir au texte pour les liens refusés
  6. Tester les mauvais cas
  7. Ne pas implémenter tout Markdown par défaut
  8. Résumé

Quand un chat IA répond Voir [Services]( /services/ ), le lien peut ne pas être rendu et le Markdown brut peut rester visible.

Acecore a rencontré ce cas dans son chat de contact, puis a ajusté le renderer dans le PR de correction du rendu des liens Markdown.

Cet article part de cette petite correction pour expliquer comment convertir les réponses IA en DOM de manière sûre.

Les réponses IA ne sont pas du HTML fiable

La sortie du modèle doit être traitée comme du texte.

Les liens, le gras et les listes sont utiles dans un chat. Mais placer la réponse dans innerHTML laisse le navigateur interpréter une chaîne produite par le modèle.

Il ne faut pas forcément implémenter tout Markdown. Il faut détecter les quelques expressions supportées et créer seulement des nœuds DOM sûrs.

Le problème ne se limite pas aux espaces

Le bug direct était un lien comme :

[Services](/services/)

Une regex stricte suppose souvent que l URL ne contient pas d espace :

/\[([^\]]+)\]\(([^)\s]+)\)/;

[^)\s]+ rejette les espaces, donc ( /services/ ) n est pas reconnu. La correction consiste à tolérer les espaces dans les parenthèses puis normaliser.

/\[([^\]]+)\]\(\s*([^)]+?)\s*\)/;

Mais assouplir le parser ne suffit pas. La valeur normalisée doit ensuite être validée.

Trimmer href avant validation

L ordre doit rester clair :

  1. Extraire label et raw href
  2. Appliquer trim() au raw href
  3. Valider le href trimé avec une allowlist
  4. Créer <a> seulement si le href est autorisé
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);
}

La valeur validée doit être la même que celle rendue dans le DOM.

La allowlist dépend du produit

Chaque site doit décider quelles URLs son IA peut afficher.

Type Exemple Décision
Chemin interne /services/ Autoriser
Même origin https://acecore.net/... Autoriser
LINE officiel https://lin.ee/... Autoriser si le canal est officiel
mailto mailto:info@acecore.net Adresse fixe uniquement
tel tel:05088902788 Numéro fixe uniquement
Autres externes Toute URL Ne pas lier par défaut
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";
}

Un site de recrutement peut autoriser des plateformes emploi. Un SaaS peut autoriser documentation et page de statut. La fonction doit refléter le produit.

Revenir au texte pour les liens refusés

Quand un lien échoue à la validation, le supprimer n est pas toujours le meilleur choix.

Dans un chat de contact, conserver le Markdown comme texte garde le contexte pour l utilisateur et aide les développeurs à voir ce que le modèle a tenté de produire.

Le renderer doit créer des liens sûrs et échouer de façon sûre.

Tester les mauvais cas

Vérifie au minimum :

Entrée Résultat attendu
[Services](/services/) Lien interne
[Services]( /services/ ) Lien interne après trim
[LINE]( https://lin.ee/example ) Lien externe autorisé
[Mauvais](javascript:alert(1)) Pas de lien
[Externe](https://example.com/) Pas de lien si le domaine est refusé
[Cassé](/services/ Affichage comme texte

Dans le PR #99, les variantes avec et sans espaces ont été vérifiées comme pointant vers la même URL attendue.

Ne pas implémenter tout Markdown par défaut

Pour un chat, le sous-ensemble peut rester simple :

  • Paragraphes
  • Listes
  • Gras
  • Code inline
  • Liens

Tables, images, HTML brut et notes de bas de page augmentent vite le périmètre. Même avec une bibliothèque, la politique HTML et URL reste à définir séparément.

Résumé

Le rendu des liens Markdown dans une réponse IA ressemble à un détail d interface, mais il fixe la frontière de confiance envers la sortie du modèle.

La règle pratique : texte d abord, petit sous-ensemble, trim avant validation, allowlist stricte et fallback sûr.

Flux de rendu des liens

  1. Text

    Traiter la réponse du modèle comme du texte brut.

  2. Parse

    Détecter uniquement le Markdown réellement supporté par le chat.

  3. Validate

    Trimmer href et accepter seulement les URLs internes ou domaines approuvés.

  4. Render

    Créer des éléments sûrs avec DOM API, pas avec innerHTML.

Décisions à séparer

Rendu trop permissif

  • Mettre les réponses IA directement dans innerHTML
  • Essayer de couvrir tout Markdown dès le départ
  • Ne pas créer de lien quand l URL contient des espaces
  • Traiter URLs externes et javascript: de la même façon

Rendu petit et sûr

  • Recevoir les réponses comme texte et convertir seulement le nécessaire en DOM
  • Supporter uniquement le sous-ensemble Markdown du chat
  • Valider les URLs après trim
  • Garder les URLs refusées comme texte

Checklist de mise en œuvre

  • Ne pas faire confiance aux réponses IA comme HTML
  • Accepter les espaces autour des URLs Markdown
  • Toujours trimmer href avant validation
  • Autoriser seulement chemins internes, origin courant et domaines nécessaires
  • Définir target et rel sur les liens externes
  • Conserver les liens refusés comme texte
  • Tester les URLs dangereuses et le Markdown cassé

Questions fréquentes

markdown-it ou marked suffit-il ?

Même avec une bibliothèque, il faut définir le traitement du HTML, les destinations de liens autorisées, target et rel, et le rejet des URLs dangereuses. Pour un chat, un petit renderer dédié peut suffire.

Autoriser les espaces autour de l URL est-il dangereux ?

Le risque ne vient pas des espaces, mais de ce qui est autorisé après trim. La validation du href normalisé garde la allowlist stricte.

Faut-il supprimer les URLs refusées ?

Les garder comme texte facilite souvent le debug et conserve le contexte. Une politique plus stricte peut aussi supprimer tout le lien.