Symptôme. Un client gérant une plateforme de formation en ligne avec des espaces membres personnalisés nous contacte, inquiet : un utilisateur affirme avoir vu, l’espace d’un instant, le tableau de progression d’un autre apprenant à la place du sien, en rechargeant simplement sa page de profil. Le support avait d’abord classé cela comme un signalement isolé, probablement une erreur de manipulation côté utilisateur. Un second signalement similaire, deux jours plus tard, change la donne : ce n’est pas une erreur de manipulation, c’est reproductible.
Diagnostic : reproduire l’incident pour comprendre sa mécanique
La reproduction en environnement de test confirme rapidement le problème : deux comptes de test, connectés simultanément depuis deux navigateurs différents, chargent la même URL /mon-espace/progression/. Selon l’ordre et le timing des requêtes, l’un des deux visiteurs reçoit parfois le contenu généré pour l’autre. Le symptôme pointe immédiatement vers un mécanisme de cache de page, puisque le site utilise une extension de cache full-page pour ses performances, une configuration standard sur ce type de plateforme à fort trafic.
En examinant la configuration de l’extension, la cause apparaît : la clé de cache utilisée pour identifier une page en cache est construite uniquement à partir de l’URL, sans tenir compte du visiteur qui la consulte. Pour une page réellement publique et identique pour tout le monde, c’est exactement le comportement souhaité — c’est même l’intérêt principal d’un cache de page. Mais /mon-espace/progression/ n’est pas une page publique : son contenu dépend entièrement du cookie de session de l’utilisateur connecté, une information que l’extension de cache, mal configurée pour ce cas précis, ignorait totalement dans la construction de sa clé.
Le mécanisme exact du mélange de contenu

Le scénario exact reconstitué avec les journaux du cache :
- Le visiteur A charge
/mon-espace/progression/en premier. L’extension de cache, ne trouvant aucune entrée existante pour cette URL, génère la page dynamiquement (avec les données spécifiques du visiteur A) et la stocke en cache sous la clépage_mon-espace-progression. - Dans la fenêtre de validité du cache (vingt minutes dans la configuration de ce client), le visiteur B charge la même URL. L’extension trouve une entrée en cache pour cette clé et la sert directement, sans régénérer la page — donc sans tenir compte du fait que le visiteur B n’est pas le visiteur A.
- Le visiteur B reçoit alors le contenu de progression du visiteur A, avec ses données personnelles de formation, tant que le cache n’expire pas ou n’est pas invalidé par une autre action.
C’est un bug de cache qui se manifeste comme une fuite de données personnelles, ce qui explique pourquoi le premier signalement a été mal interprété : le symptôme ressemble à un dysfonctionnement d’affichage aléatoire, pas immédiatement à un problème de sécurité.
Le correctif : exclure la page du cache de manière ciblée
La correction la plus sûre pour une page dont le contenu dépend systématiquement du visiteur consiste à l’exclure entièrement du cache de page, plutôt que de tenter une clé de cache par utilisateur (ce qui annulerait de toute façon le bénéfice principal du cache pour ce type de contenu, tout en gardant un risque résiduel si l’exclusion de certains fragments est oubliée) :
// Exclusion explicite des URLs sensibles au niveau utilisateur,
// à adapter selon les hooks fournis par l'extension de cache utilisée.
add_filter( 'exemple_cache_exclusion_urls', function( $urls_exclues ) {
$urls_exclues[] = '/mon-espace/*';
return $urls_exclues;
} );
// Vérification défensive complémentaire, indépendante de l'extension :
// ne jamais laisser une page connectée être mise en cache, quel que soit
// le mécanisme de cache actif sur le serveur.
add_action( 'template_redirect', function() {
if ( is_user_logged_in() && ( is_page( 'mon-espace' ) || is_page( 'progression' ) ) ) {
nocache_headers();
}
} );
nocache_headers() est une fonction native de WordPress qui envoie l’ensemble des en-têtes HTTP nécessaires pour empêcher la mise en cache côté navigateur, proxy intermédiaire et la plupart des extensions de cache respectueuses des standards — Cache-Control: no-cache, no-store notamment. Elle ne remplace pas la configuration propre de l’extension de cache, mais constitue une seconde barrière utile en cas d’oubli de configuration ailleurs, ou si une nouvelle extension de cache est installée plus tard sans reprendre la même liste d’exclusions.
Distinguer le cache de page du cache d’objet
Ce correctif concerne exclusivement le cache de page complète (full-page cache), qui stocke le HTML final envoyé au navigateur. Le cache d’objet de WordPress (via wp_cache_get() et wp_cache_set(), typiquement backé par Redis ou Memcached) fonctionne différemment : il met en cache des résultats de requêtes ou de calculs internes, généralement déjà associés à l’identifiant de l’utilisateur dans la clé lorsque le développeur l’a prévu correctement. Le même type d’erreur — oublier le contexte utilisateur dans la clé — reste possible à ce niveau, mais avec des conséquences et un diagnostic différents, non traités dans ce cas précis.
Prévention : tester systématiquement en double session
Depuis cet incident, tout écran affichant des données propres à un utilisateur connecté est désormais testé en conditions doubles avant mise en production : deux comptes différents, deux navigateurs (ou un navigateur classique et une fenêtre de navigation privée), chargeant la même URL à quelques secondes d’intervalle, avec vérification que chacun reçoit bien son propre contenu.
Sur ce projet, ce test en double session a depuis révélé un second cas similaire sur une page de panier avant même qu’un client ne le signale, confirmant que ce type d’erreur de configuration de cache est plus fréquent qu’il n’y paraît sur les sites à fort trafic.
Ce que ce cas ne couvre pas
Ce diagnostic ne traite pas de la sécurité des cookies d’authentification WordPress eux-mêmes (leur scope, leur durée, leur protection contre le vol), un sujet distinct traité séparément. Le problème ici ne venait pas d’un cookie mal protégé, mais d’un mécanisme de cache qui ignorait purement et simplement son existence au moment de décider quoi servir.