# Interactivity API et sécurité : ce que l’état serveur expose au navigateur

> L'état initial d'un bloc interactif est sérialisé en clair dans le HTML de la page. Un détail technique qui peut virer à la fuite de données si on ne le sait pas.

- Auteur : Clément Hadrot
- Publié le : 2024-09-13
- Mis à jour le : 2024-09-13
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/interactivity-api-securite-etat-serveur-expose-navigateur/

## L’essentiel

- wp_interactivity_state sérialise son contenu dans un script JSON public
- Toute donnée passée y devient visible dans le code source
- Le filtrage doit se faire avant l'appel, jamais après

Un développeur nous a montré fièrement son bloc interactif construit avec la nouvelle Interactivity API, qui affichait un badge « déjà acheté » ou « en stock limité » selon des données récupérées côté serveur. En ouvrant le code source de la page dans le navigateur, on retrouvait dans un bloc `script` de type `application/json` non seulement ces deux informations, mais aussi l'identifiant interne complet de la commande utilisée pour le calcul, jamais destiné à quitter le serveur.

Ce n'est pas un bug de l'Interactivity API : c'est son fonctionnement normal, mal compris. Comprendre précisément ce que `wp_interactivity_state()` expose, et pourquoi, évite ce genre de fuite qui passe facilement inaperçue tant qu'on ne pense pas à vérifier le code source rendu.

## Comment l'état initial atteint le navigateur

L'Interactivity API, introduite avec WordPress 6.5, permet de définir un état côté serveur au moment du rendu d'un bloc, puis de l'hydrater côté client pour que du JavaScript puisse l'utiliser sans nouvel appel réseau. Ce mécanisme repose sur `wp_interactivity_state()`, qui enregistre des données associées à un espace de nom, sérialisées automatiquement par WordPress dans une balise `<script type="application/json">` insérée directement dans le HTML de la page.

```
wp_interactivity_state( 'mon-plugin', array(
    'produit_id'      => $produit_id,
    'stock_restant'   => $stock_restant,
    'commande_id_ref' => $commande->get_id(), // fuite involontaire
) );
```

Chacune de ces valeurs se retrouve littéralement dans le code source de la page, consultable par n'importe quel visiteur via un simple clic droit « afficher le code source », sans nécessiter le moindre outil d'interception réseau. Il n'y a ici aucune faille technique au sens classique : c'est le comportement documenté de la fonction, mais son implication en matière de confidentialité des données est facile à sous-estimer.

## Ce qui distingue cette situation d'un simple appel AJAX

> L'essentiel à retenir : wp_interactivity_state sérialise son contenu dans un script JSON public ; Toute donnée passée y devient visible dans le code source ; Le filtrage doit se faire avant l'appel, jamais après

Avec un appel AJAX classique, la donnée transite par le réseau et n'apparaît dans les outils de développement du navigateur que si on va explicitement inspecter les requêtes. Avec `wp_interactivity_state()`, la donnée est imprimée directement dans le HTML statique de la page, visible sans aucune manipulation, y compris par des robots d'indexation ou des outils d'archivage qui capturent le code source des pages visitées.

Cette différence a une conséquence concrète pour la conception : toute donnée passée à cette fonction doit être traitée comme publique dès l'instant où elle est enregistrée, quelle que soit la logique d'affichage prévue côté JavaScript pour la cacher visuellement.

## Les catégories de données à ne jamais y placer

- Identifiants internes de base de données qui ne servent qu'au calcul serveur (identifiants de commande, de client, de transaction).
- Informations personnelles même partielles (fragment d'adresse e-mail, nom complet) qui ne sont pas déjà destinées à l'affichage public de la page.
- Jetons, clés ou tout élément qui ressemble à un secret, même temporaire.
- Données de tarification ou de remise réservées à un segment de clientèle précis, si leur présence dans le code source pouvait avantager un visiteur non concerné.

## Filtrer avant l'appel, jamais après

Le correctif ne consiste pas à masquer côté JavaScript une donnée déjà présente dans le HTML : à ce stade, elle est déjà exposée, quel que soit l'affichage qui en est fait ensuite. Le filtrage doit intervenir avant l'appel à `wp_interactivity_state()`, en ne transmettant que le strict nécessaire à l'interactivité voulue :

```
wp_interactivity_state( 'mon-plugin', array(
    'produit_id'    => $produit_id,
    'stock_restant' => $stock_restant > 5 ? 'en_stock' : 'stock_limite',
) );
```

Ici, plutôt que de transmettre le nombre exact d'unités en stock (une donnée métier parfois sensible commercialement) ou un identifiant de commande interne, seule une valeur déjà simplifiée pour l'affichage est exposée, ce qui réduit la surface d'information réellement publiée.

## Le cas des données par utilisateur connecté

Un piège additionnel apparaît avec le cache de page complet (full page cache), fréquent en production : un état initial calculé pour un utilisateur connecté peut se retrouver mis en cache et servi tel quel à un visiteur différent si la logique de mise en cache ne distingue pas correctement les contextes utilisateur. Toute donnée dépendante de l'utilisateur connecté placée dans `wp_interactivity_state()` doit donc soit être exclue du cache de page pour ce bloc précis, soit être recalculée côté client via un appel à l'API REST après le rendu initial, plutôt que d'être figée dans le HTML mis en cache.

## Vérifier concrètement ce qui est exposé

Un réflexe simple avant de mettre en production un bloc utilisant l'Interactivity API : ouvrir le code source rendu de la page (pas les outils de développement, le vrai code source servi) et rechercher la balise `script type="application/json"` correspondant à l'espace de nom du bloc, pour lister exactement ce qui y figure. Cette vérification prend moins d'une minute et révèle immédiatement toute donnée qui n'aurait pas dû s'y trouver.

> Notre règle depuis cet incident : tout champ ajouté à `wp_interactivity_state()` passe par la question « suis-je à l'aise de voir cette valeur dans le code source public de la page ? ». Si la réponse hésite, le champ ne rentre pas dans l'état initial.

## En résumé

L'Interactivity API ne comporte pas de faille en elle-même : elle fait exactement ce que sa documentation annonce, sérialiser un état dans le HTML pour l'hydratation côté client. Le risque vient de la confusion entre « donnée utile côté client » et « donnée destinée à être publique », une distinction à faire systématiquement avant chaque appel à `wp_interactivity_state()` plutôt qu'après coup.
