# Un scoring de leads par IA exposait ses scores à un partenaire commercial

> Une extension de scoring de prospects par IA laissait un partenaire commercial consulter les scores calculés pour les leads d'une autre société du même groupe. Retour sur la faille et son correctif.

- Auteur : Clément Hadrot
- Publié le : 2025-08-04
- Mis à jour le : 2025-08-04
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/scoring-leads-ia-scores-exposes-partenaire/

## L’essentiel

- Repérer une route REST sans filtre de compte
- Comprendre le risque du cloisonnement absent
- Corriger par une clause d'appartenance systématique

« Pourquoi vois-je des scores de prospects qui ne sont pas les nôtres ? » Cette question, posée par un commercial d'une filiale à son support technique, a déclenché l'audit qui fait l'objet de ce retour d'expérience. Le groupe en question exploitait une extension WordPress de scoring de leads par intelligence artificielle, mutualisée entre plusieurs sociétés partageant la même infrastructure d'hébergement mais opérant des activités commerciales distinctes.

Chaque société du groupe disposait de son propre compte au sein de l'extension, censé cloisonner ses prospects et les scores calculés par le modèle. Dans les faits, un partenaire commercial externe, disposant d'un accès limité en lecture seule à ses propres scores, pouvait consulter ceux calculés pour les prospects d'une autre société du groupe simplement en modifiant un identifiant dans l'URL de la requête API.

## Symptôme : un identifiant numérique trop prévisible dans l'URL

La route REST exposée par l'extension suivait un schéma classique : `/wp-json/scoring/v1/leads/{compte_id}/scores`. L'identifiant de compte était un entier auto-incrémenté, attribué séquentiellement à chaque nouvelle société inscrite sur la plateforme. Un partenaire curieux, en changeant simplement ce chiffre dans l'URL, obtenait une réponse JSON complète contenant les scores et certaines métadonnées des prospects d'un autre compte.

L'anomalie a été repérée par hasard, lors d'un test manuel effectué pour vérifier un tout autre comportement de l'API. Aucune alerte automatisée n'avait signalé ce comportement, faute de surveillance sur les schémas d'accès à cette route spécifique.

## Diagnostic : une autorisation vérifiant l'authentification mais pas l'appartenance

L'examen du code de la fonction de rappel associée à la route a révélé la cause exacte : la fonction `permission_callback` vérifiait uniquement que la requête provenait d'un utilisateur authentifié disposant d'un jeton API valide, sans jamais comparer l'identifiant de compte demandé à celui associé à ce jeton.

> L'essentiel à retenir : Repérer une route REST sans filtre de compte ; Comprendre le risque du cloisonnement absent ; Corriger par une clause d'appartenance systématique

```
'permission_callback' => function ( $request ) {
    // Vérifie l'authentification, pas l'appartenance au compte demandé
    return is_user_logged_in() || rest_api_valid_token( $request );
},
```

Ce type d'erreur est fréquent dans les architectures multi-comptes construites progressivement : l'authentification a été mise en place en premier, l'autorisation fine par ressource est arrivée plus tard, et un endpoint plus ancien est parfois passé entre les mailles du filet lors de la généralisation du contrôle d'appartenance.

## Correctif : une clause d'appartenance systématique avant toute réponse

La correction a consisté à ajouter, dans chaque fonction de rappel exposant des données liées à un compte, une vérification explicite que l'identifiant de compte demandé correspond bien à celui associé au jeton ou à l'utilisateur authentifié :

```
'permission_callback' => function ( $request ) {
    $compte_token   = rest_api_get_compte_id_from_token( $request );
    $compte_demande = (int) $request->get_param( 'compte_id' );

    return $compte_token && $compte_token === $compte_demande;
},
```

Au-delà de ce correctif ponctuel, l'équipe a mené une revue systématique de toutes les routes REST exposées par l'extension, à la recherche d'autres endpoints présentant le même défaut de cloisonnement.

## Prévention : tester le cloisonnement à chaque nouvelle route

Ce type de faille se prévient efficacement par un test systématique et documenté à chaque ajout de route REST touchant à une ressource associée à un compte :

- Créer deux comptes de test distincts avant toute mise en production d'une nouvelle route.
- Tenter délibérément d'accéder aux données du compte B avec les identifiants du compte A.
- Automatiser ce test dans la suite d'intégration continue plutôt que de le réserver à une vérification manuelle ponctuelle.

L'utilisation d'identifiants séquentiels prévisibles aggrave également ce type de faille : remplacer les identifiants de compte auto-incrémentés par des identifiants UUID rend l'énumération manuelle beaucoup plus difficile, sans pour autant remplacer un contrôle d'appartenance correctement implémenté.

> Un identifiant qui se devine ne protège rien à lui seul, mais son absence de prévisibilité gagne un temps précieux avant qu'une faille de cloisonnement soit découverte et exploitée à grande échelle.

## En résumé

Cette faille illustre un piège classique des extensions multi-comptes : une authentification fonctionnelle qui masque l'absence de vérification d'appartenance sur une ressource précise. La pertinence du modèle de scoring lui-même n'entre pas en ligne de compte ici ; ce qui compte, c'est que chaque route exposant des données propres à un compte vérifie explicitement cette appartenance avant de répondre, et que cette vérification soit testée systématiquement plutôt que supposée acquise une fois pour toutes.
