Une simple requête d’introspection GraphQL a suffi à révéler le problème : un champ nommé adresse_facturation, censé rester strictement interne à la gestion administrative d’un annuaire de praticiens, apparaissait dans le schéma public consommé par le front de recherche. N’importe quel visiteur capable de lire une documentation GraphQL pouvait interroger ce champ pour l’ensemble des 340 fiches publiées.
Ce type de fuite ne provient presque jamais d’une intention malveillante côté développement, mais d’un réflexe habituel avec les champs personnalisés ACF : dès qu’un champ est ajouté au groupe de champs d’un type de contenu, WPGraphQL for ACF l’expose automatiquement dans le schéma, sans distinction entre une donnée destinée au front public et une donnée strictement administrative.
Le problème : l’exposition par défaut des champs ACF
Le groupe de champs personnalisés associé au type praticien contenait, pêle-mêle, des informations pensées pour un usage back-office (adresse de facturation, numéro de SIRET, notes internes de validation) et des informations destinées à l’affichage public (spécialité, ville d’exercice, disponibilités). WPGraphQL for ACF ne fait pas cette distinction automatiquement : tout champ activé pour l’affichage dans le groupe ACF devient, par défaut, un champ interrogeable du schéma GraphQL, sans notion de visibilité par rôle intégrée nativement.
Le front de recherche, développé en Astro, n’interrogeait effectivement que les champs publics dans ses requêtes. Rien n’empêchait cependant un tiers d’écrire sa propre requête ciblant directement adresseFacturation, dès lors que ce champ figurait dans le schéma exposé publiquement à l’endpoint /graphql.
Diagnostic par introspection
La méthode de vérification la plus rapide consiste à interroger le schéma lui-même :
query {
__type(name: "Praticien") {
fields {
name
}
}
}

Cette requête, envoyée sans aucune authentification, a listé l’intégralité des champs disponibles sur le type, révélant immédiatement la présence de champs qui n’auraient jamais dû sortir du back-office.
Correctif : retirer le champ du schéma public
Deux approches ont été envisagées. La première, désactiver purement et simplement l’exposition GraphQL du champ dans la configuration ACF, a été écartée car un usage interne au back-office nécessitait malgré tout d’y accéder via une requête authentifiée pour un outil de gestion administrative séparé. La seconde approche, retenue, restreint l’accès selon le contexte de la requête grâce au filtre graphql_object_fields :
add_filter('graphql_object_fields', function ($fields, $type_name) {
if ($type_name !== 'Praticien') {
return $fields;
}
$champs_sensibles = ['adresseFacturation', 'numeroSiret', 'notesInternesValidation'];
foreach ($champs_sensibles as $champ) {
if (!is_user_logged_in() || !current_user_can('manage_options')) {
unset($fields[$champ]);
}
}
return $fields;
}, 10, 2);
Avec ce filtre, une requête non authentifiée reçoit désormais une erreur explicite de type Cannot query field sur ces champs, plutôt que de simplement recevoir une valeur vide qui aurait pu masquer le problème sans le résoudre.
Aller plus loin : un audit systématique du schéma
Ce type de fuite justifie la mise en place d’un contrôle automatisé, exécuté à chaque déploiement, comparant la liste des champs exposés du schéma public à une liste blanche validée par l’équipe. Un script CI simple, interrogeant l’endpoint d’introspection et comparant le résultat à un fichier de référence versionné, suffit à détecter tout nouveau champ ajouté par erreur avant sa mise en production.
- Extraction du schéma via une requête d’introspection standard
- Comparaison avec une liste blanche de champs autorisés par type
- Échec du pipeline de déploiement en cas d’écart non validé
Ce que cet incident ne couvre pas
Cet audit s’est volontairement limité à la question de l’exposition technique du schéma. Les praticiens exerçant dans le secteur de la santé, la question plus large de la conformité RGPD sur les données de santé au sens strict relève d’un traitement à part, avec ses propres bases légales et exigences de sécurisation renforcée, qui dépasse le cadre de ce correctif ponctuel.
En résumé
WPGraphQL for ACF expose par défaut tout champ activé dans un groupe de champs personnalisés, sans distinction implicite entre données publiques et données internes. Un contrôle d’accès explicite par champ, couplé à un audit régulier du schéma, reste la seule garantie fiable contre ce type de fuite silencieuse, bien plus discrète qu’une faille applicative classique.