Quarante pour cent de remise partenaire, visible en clair dans une réponse JSON accessible sans aucune authentification : c’est ce qu’a découvert un office de tourisme en examinant, par curiosité, ce que retournait l’API REST de son propre site WordPress sur le point de terminaison listant ses hébergements partenaires. La conception de cette API elle-même, pensée à l’origine pour alimenter une application mobile de recherche de logements, ne fait pas l’objet de cet article : le problème ici tient à un champ de métadonnées qui n’aurait jamais dû s’y trouver.
Les hébergements du territoire étaient enregistrés comme un type de contenu personnalisé, avec des champs personnalisés gérés via Advanced Custom Fields : nom, adresse, photos, mais aussi un champ interne tarif_negocie_office, utilisé uniquement pour le calcul de commissions dans le back-office, jamais destiné à un affichage public. Le problème : ce champ, comme tous les champs ACF associés au type de contenu, était automatiquement exposé dans la réponse de l’API REST par défaut, dès lors que le type de contenu avait show_in_rest activé pour alimenter l’application mobile.
Pourquoi show_in_rest expose plus qu’on ne le pense
Activer show_in_rest sur un type de contenu personnalisé rend ses champs natifs accessibles via /wp-json/wp/v2/hebergements. Si ce type de contenu intègre également des champs ACF avec l’option « Afficher dans l’API REST » cochée par commodité lors de la création du champ, chacun de ces champs se retrouve dans la réponse JSON publique, sans distinction entre un champ destiné à l’affichage et un champ purement interne.
Ce que révélait un simple appel à l’API

curl https://exemple-office-tourisme.fr/wp-json/wp/v2/hebergements/842
La réponse contenait, entre les champs attendus, la ligne suivante :
"acf": {
"nom_hebergement": "Le Clos des Vignes",
"tarif_negocie_office": "40%"
}
N’importe quel concurrent, ou l’hébergeur partenaire d’un autre établissement, pouvait consulter ce taux de remise simplement en connaissant l’identifiant du contenu, obtenu par un parcours trivial de la liste publique des hébergements.
Corriger : retirer le champ de la réponse REST sans le supprimer du back-office
La correction ne consistait pas à supprimer le champ, toujours nécessaire en interne, mais à l’exclure explicitement de la réponse REST, via un filtre sur la préparation de la réponse :
add_filter( 'rest_prepare_hebergement', function( $response, $post, $request ) {
$donnees = $response->get_data();
unset( $donnees['acf']['tarif_negocie_office'] );
$response->set_data( $donnees );
return $response;
}, 10, 3 );
Ajouter un permission_callback même sur des données qui paraissent publiques
Au-delà de ce champ précis, l’audit a révélé une lacune plus générale : la route REST personnalisée créée pour l’application mobile ne définissait aucun permission_callback, se contentant de la valeur par défaut, ouverte à tous. Même quand la majorité des données affichées sont effectivement publiques (nom, adresse, photos), l’absence de permission_callback explicite empêche toute évolution future du contenu de la route sans revoir chaque champ un par un :
register_rest_route( 'office/v1', '/hebergements', array(
'methods' => 'GET',
'callback' => 'office_get_hebergements',
'permission_callback' => '__return_true',
) );
Déclarer explicitement __return_true plutôt que de laisser le paramètre absent rend l’intention lisible et vérifiable lors d’un futur audit, plutôt que de laisser planer un doute sur un oubli.
Un champ de métadonnées interne ne devient jamais public par accident sans raison : chaque champ ACF exposé à l’API REST mérite d’être validé un par un, jamais activé en bloc par confort.
Notre verdict
Le taux de remise négocié n’a de valeur que tant qu’il reste confidentiel entre l’office de tourisme et l’hébergeur. L’exposer par un simple oubli de configuration ACF, sans même un accès authentifié requis, aurait pu compromettre des mois de négociations commerciales. Le correctif tient en quelques lignes ; l’audit qui l’a révélé, lui, a pris une matinée.