Cross-site scripting apparaît lorsqu’une donnée destinée à être affichée devient du contenu exécutable dans le navigateur. La valeur peut venir d’un formulaire, d’une URL, d’une base, d’une API ou d’une dépendance compromise. L’output encoding est une défense majeure, mais il fonctionne seulement s’il correspond au contexte: texte HTML, attribut, URL, JavaScript et CSS ont des grammaires différentes.
Le navigateur parse plusieurs langages
Un document HTML contient du markup, des scripts, des styles et des destinations de navigation. Une valeur sûre entre deux balises peut être dangereuse dans une chaîne JavaScript. Une valeur bien encadrée dans un attribut peut encore former une URL javascript:.
La sécurité dépend donc de l’endroit où les bytes seront interprétés. Les frameworks protègent les cas courants, mais raw HTML, innerHTML et handlers inline contournent souvent ces garanties.
Échappez à la frontière finale
Stocker l’input déjà échappé mélange données et présentation. La même valeur peut ensuite aller dans JSON, e-mail ou CSV, où les règles HTML n’ont plus de sens. Conservez la valeur sémantique et encodez au rendu.
Le template connaît le contexte. Le rendu raw doit rester rare, visible en revue et réservé au HTML assaini.
Évitez les contextes dangereux
Insérer des données non fiables dans des blocs script, style, attributs d’événement ou fragments HTML est difficile à sécuriser. Préférez JSON, data attributes sûrs et APIs DOM structurées. Pour du texte, utilisez textContent.
La meilleure défense est de ne pas donner aux données l’occasion de devenir du code. L’encodage devient alors une opération de frontière claire.
Le HTML autorisé demande un sanitizer
Commentaires enrichis et éditeurs WYSIWYG peuvent nécessiter certains tags. Un sanitizer maintenu doit parser le HTML et appliquer une allowlist d’éléments, attributs et schémas d’URL. Il doit gérer le HTML mal formé comme le navigateur le ferait.
Les regex ne sont pas un sanitizer fiable. HTML est trop tolérant et structuré pour une liste de remplacements.
Les URLs demandent une validation de sens
Échapper les guillemets garde une valeur dans href, mais ne rend pas la destination acceptable. Il faut valider schémas, hôtes ou chemins autorisés. Un open redirect peut être parfaitement encodé et rester dangereux.
Validez d’abord la politique de navigation, puis encodez pour l’attribut. Ce sont deux contrôles différents.
CSP limite les dégâts
Content Security Policy peut bloquer des scripts inattendus et produire des rapports. Nonces et hashes réduisent l’exécution inline. Mais CSP reste une défense en profondeur. Une politique permissive ou legacy ne remplace pas l’escaping.
Le cœur de la prévention reste contextual encoding, DOM APIs sûres et sanitization contrôlée.
Le client peut introduire XSS après le rendu serveur
Une page initiale peut être sûre, puis un script lit une valeur et la place dans innerHTML. Les audits doivent couvrir les DOM sinks, widgets tiers et helpers personnalisés. Trusted Types peut aider sur de grandes applications.
Les tests doivent regarder le DOM final, pas seulement la réponse HTML. Le JavaScript peut changer le contexte après coup.
Stored XSS peut dormir longtemps
Un payload stocké dans un profil, ticket ou import peut s’exécuter des mois plus tard dans un panneau admin. Chaque sortie doit traiter les données stockées comme non fiables. Les interfaces internes ne sont pas moins sensibles; elles sont souvent plus privilégiées.
Après un incident, supprimer la valeur ne suffit pas. Il faut trouver le sink, ajouter un test avec le payload, invalider les sessions exposées si nécessaire et vérifier les composants similaires.
Les migrations de framework demandent un audit
Changer de template engine ou de framework client peut modifier les règles d’auto-escaping. Un helper qui attendait du texte peut commencer à recevoir du HTML ou l’inverse. Avant migration, inventoriez raw outputs, sanitizers et DOM sinks dangereux.
Les tests doivent observer le DOM final après exécution client. Une réponse serveur sûre peut devenir vulnérable lorsqu’un composant réinsère une valeur avec innerHTML.
Les outils internes doivent échapper les payloads
Un dashboard de sécurité ne doit pas exécuter la charge qu’il affiche. Les logs et rapports doivent montrer les valeurs suspectes comme texte échappé. Sinon l’outil d’investigation devient une nouvelle surface XSS.
Les rapports CSP et alertes sanitizer doivent suivre la même règle. Ils manipulent souvent des chaînes hostiles par définition; leur affichage doit donc être plus strict que celui d’une page ordinaire.
Les exemples de payload utilisés dans la documentation interne doivent eux aussi être neutralisés. Un copier-coller depuis un guide ne devrait jamais créer une exécution accidentelle.
Après chaque correction, le payload d’origine doit rejoindre une suite de régression exécutée dans un vrai navigateur. Cette preuve protège mieux qu’un commentaire indiquant simplement que le cas a déjà été rencontré.
Préserver la frontière données-code
La prévention XSS n’est pas la mémorisation d’une fonction d’échappement. C’est une architecture où les données restent des données jusqu’au contexte final, où le HTML autorisé est typé et assaini, et où les APIs dangereuses sont rares.
Quand cette frontière est visible dans les templates, composants et revues, XSS devient beaucoup plus difficile à réintroduire accidentellement.