Trente-six mille dossiers patients transitent chaque mois par une extension de prise de rendez-vous en ligne pour un réseau de centres de soins. Chaque erreur applicative génère un message envoyé à Sentry pour le suivi technique, et chaque webhook manqué déclenche une notification vers un canal de support externe. Le problème arrive dès la première ligne de code non relue : un message d’erreur qui contient, sans y penser, le nom complet et le numéro de sécurité sociale partiel d’un patient.
Ce n’est pas un cas théorique. Les messages d’exception PHP incluent par défaut la trace complète des variables locales dans certains contextes de débogage, et un développeur pressé insère volontiers print_r( $rendez_vous ) dans un message de log « pour comprendre le bug plus tard ». Cet article décrit une architecture de filtrage systématique, appliquée avant tout envoi vers un service externe, sans traiter la question distincte — et tout aussi critique — de l’hébergement HDS lui-même.
Centraliser la liste des champs sensibles
La première règle est de ne jamais laisser chaque appel de log décider seul de ce qu’il doit filtrer. Une liste unique de clés sensibles, maintenue à un seul endroit, réduit le risque d’oubli :
final class WPM_Champs_Sensibles {
public const LISTE = array(
'nom', 'prenom', 'email', 'telephone',
'numero_secu', 'date_naissance', 'adresse',
'notes_praticien', 'motif_consultation',
);
}
Cette classe devient la référence unique consultée par tout code qui prépare une charge utile à destination d’un service tiers, qu’il s’agisse de Sentry, d’un webhook Slack interne ou d’un export de diagnostic.
Un filtre de rédaction appliqué récursivement
Le filtre doit parcourir n’importe quelle structure de données, y compris des tableaux imbriqués représentant des objets de rendez-vous complexes, et remplacer chaque valeur sensible par un marqueur explicite plutôt que de simplement la supprimer, ce qui casserait la lecture du contexte par l’équipe technique.

function wpm_rediger_contexte( array $donnees ) : array {
$sensibles = WPM_Champs_Sensibles::LISTE;
array_walk_recursive( $donnees, function ( &$valeur, $cle ) use ( $sensibles ) {
if ( in_array( $cle, $sensibles, true ) ) {
$valeur = '[REDACTED]';
}
} );
return $donnees;
}
add_filter( 'wpm_avant_envoi_sentry', 'wpm_rediger_contexte' );
add_filter( 'wpm_avant_envoi_webhook_support', 'wpm_rediger_contexte' );
Le choix d’un filtre WordPress plutôt que d’une fonction appelée manuellement à chaque endroit garantit qu’aucun nouveau point d’envoi ajouté plus tard par un autre développeur ne peut oublier l’étape de rédaction — à condition que le point d’envoi respecte, lui, la convention d’appeler ce filtre avant toute transmission.
Le cas particulier des traces d’exception
Une exception PHP capturée via un gestionnaire global expose potentiellement l’intégralité de la pile d’appel, variables locales comprises selon le niveau de détail configuré. La bonne pratique consiste à ne jamais transmettre l’objet exception brut, mais un résumé construit explicitement :
set_exception_handler( function ( \Throwable $e ) {
$resume = array(
'message' => $e->getMessage(),
'fichier' => basename( $e->getFile() ),
'ligne' => $e->getLine(),
);
wpm_envoyer_vers_sentry( wpm_rediger_contexte( $resume ) );
} );
Tester la non-fuite, pas seulement l’espérer
- Un test unitaire injecte un jeu de données factices contenant chaque champ sensible connu, puis vérifie que le résultat filtré ne contient plus aucune de ces valeurs.
- Un test de non-régression tourne à chaque modification du filtre pour garantir qu’un renommage de clé (par exemple
numero_secudevenunumero_secu_partiel) n’échappe pas silencieusement à la liste. - Une revue de code impose qu’aucun appel direct à l’API du service tiers ne contourne le filtre central.
La consigne donnée à l’équipe sur ce projet tient en une phrase : si un champ n’est pas explicitement listé comme sûr à exporter, il est traité comme sensible par défaut, jamais l’inverse.
En résumé
Anonymiser après coup, en croisant les doigts pour qu’aucun développeur n’oublie, ne suffit pas sur une extension santé. La seule architecture défendable devant un audit RGPD est celle où le filtrage est structurel : une liste de champs sensibles centralisée, un filtre appliqué systématiquement avant tout départ vers un service tiers, et des tests qui vérifient concrètement l’absence de fuite plutôt que de se contenter d’une relecture manuelle. Ce coût d’ingénierie, payé une fois, protège ensuite chaque nouvelle fonctionnalité ajoutée à l’extension.