En voulant activer le module Runlight en production, je ne comprenais pas pourquoi le nombre de visiteurs restait toujours à zéro. Après plusieurs sessions de débogage avec Drupal et l’onglet Réseau du navigateur, j’ai constaté qu’au lieu d’obtenir une réponse 202, comme en développement, je recevais une réponse 405.
Le module était installé, le script JavaScript se chargeait correctement et la page s’affichait normalement. Pourtant, les données n’arrivaient pas dans les tables locales de Drupal. Le problème ne se trouvait pas dans la configuration apparente de Runlight, mais dans une couche située devant Drupal : ModSecurity, avec les règles OWASP Core Rule Set de l’hébergement cPanel, bloquait le format réel de la requête.
Le diagnostic : le script fonctionne, mais pas l’envoi
Un compteur à zéro ne signifie donc pas nécessairement qu’aucune visite n’a eu lieu. C’est l’onglet Réseau qui a permis d’identifier le point de rupture : la requête envoyée au point de terminaison /runlight/e recevait une réponse 405 en production. Le problème concernait donc l’envoi de l’événement, et non seulement le chargement du script.
Cette requête POST vers /runlight/e contenait bien un événement de type pageview, l’URL visitée, un identifiant de session et d’autres informations de navigation non sensibles. En production, c’est précisément cette requête qui recevait une réponse 405 et le message method_not_allowed. Une reproduction avec Drush a toutefois révélé une différence importante : une requête utilisant application/json recevait 202, tandis que la requête reproduisant le comportement réel du navigateur, avec Content-Type: text/plain;charset=UTF-8, recevait elle aussi 405.
La cause était donc isolée : le beacon envoyé par le navigateur avec ce type de contenu était refusé avant que Runlight puisse le traiter. Le mécanisme précis utilisé par le script importe moins ici que le format effectivement observé dans la requête.
ModSecurity intervenait avant Drupal
Sur un hébergement cPanel, la requête suit généralement un chemin semblable à celui-ci :
navigateur → Apache / ModSecurity → Drupal / Runlight → base de données
ModSecurity est un pare-feu applicatif qui inspecte les méthodes HTTP, les URL, les en-têtes, les types de contenu et les corps de requête. L’OWASP Core Rule Set fournit des règles générales contre les injections, les anomalies de protocole et d’autres comportements suspects.
Dans ce cas, la règle 920420, liée au contrôle du type de contenu, ajoutait l’anomalie qui concernait le text/plain. La règle 949110, également présente dans les journaux, appliquait ensuite le blocage final en fonction du score. Elle n’était donc pas la cause initiale. La désactiver seule aurait masqué le problème et affaibli la protection.
Une exception limitée à l’endpoint Runlight
La correction a été ajoutée dans le fichier utilisateur de ModSecurity, plutôt que dans une règle gérée automatiquement par cPanel ou directement dans le CRS :
# Runlight : autoriser le beacon text/plain sur son endpoint SecRule REQUEST_URI "@streq /runlight/e" "id:1009204,phase:1,pass,nolog,ctl:ruleRemoveById=920420"
Cette exception ne désactive pas ModSecurity pour le site entier. Elle retire uniquement la règle 920420 lorsque l’URL correspond exactement à /runlight/e. Les autres routes continuent donc d’être inspectées normalement.
Avant toute modification, il faut vérifier les droits root, confirmer le fichier réellement inclus, choisir un identifiant personnalisé disponible et créer une sauvegarde. Les chemins peuvent varier selon les versions de cPanel, EasyApache, Apache et ModSecurity.
Après la modification, apachectl -t doit confirmer que la configuration est valide. Il faut ensuite vérifier dans le navigateur que le POST vers /runlight/e retourne 202, puis confirmer avec Drush qu’un événement apparaît dans les tables Runlight. Les journaux ModSecurity permettent enfin de vérifier que l’exception fonctionne sans désactiver les autres protections.
La leçon
Un module Drupal peut être correctement installé et une page peut se charger normalement, tout en produisant un compteur vide. Le point de rupture peut se situer dans l’infrastructure, avant même que Drupal reçoive la requête.
La reproduction exacte du beacon — et non une simple requête JSON ressemblante — a permis d’identifier le problème. Lorsqu’une application utilise un format inhabituel mais valide, une exception ciblée et documentée est préférable à la désactivation générale du WAF ou de l’OWASP CRS.