# Assainir le contenu injecté par dangerouslySetInnerHTML dans la fonction save() d’un bloc personnalisé

> Un bloc Gutenberg qui construit son HTML dynamiquement peut sauvegarder du contenu non filtré. Voici comment le sécuriser sans casser le rendu.

- Auteur : WordPress Développement
- Publié le : 2023-02-10
- Mis à jour le : 2023-02-10
- Catégorie : Sécurité
- URL : https://www.wpmoderne.fr/securite/assainir-dangerouslysetinnerhtml-bloc-gutenberg/

## L’essentiel

- Ne jamais injecter une valeur d'attribut brute dans save()
- Filtrer côté serveur en complément du filtrage côté client
- Tester avec des caractères hostiles avant publication

`dangerouslySetInnerHTML` porte bien son nom : cette propriété React indique explicitement au moteur de rendu de désactiver l'échappement automatique du contenu. Dans un bloc Gutenberg personnalisé, elle apparaît naturellement dès qu'un développeur veut insérer du HTML généré dynamiquement — un extrait enrichi, une liste construite depuis une source externe, un rendu Markdown converti côté client. Le problème survient quand cette valeur provient, même indirectement, d'une saisie utilisateur non filtrée.

La fonction `save()` d'un bloc génère le HTML statique qui sera enregistré en base de données dans le contenu du post. Contrairement à `render_callback` côté PHP, ce HTML n'est pas régénéré à chaque affichage : il est figé au moment de la sauvegarde, et WordPress ne le repasse pas systématiquement par `wp_kses_post()` à l'affichage pour un bloc statique. Une valeur non filtrée à ce stade reste donc présente telle quelle dans le contenu publié.

## Le scénario qui pose problème

Prenons un bloc « citation sourcée » qui permet à l'auteur de saisir un texte de citation via un `RichText`, puis de composer une légende contenant un lien généré dynamiquement à partir d'un champ texte libre. Si la fonction `save()` construit la légende ainsi :

```
save() {
  const { legende } = attributes;
  return (
    <div dangerouslySetInnerHTML={ { __html: legende } } />
  );
}
```

et que `legende` provient d'un champ texte simple (pas d'un `RichText` qui applique son propre nettoyage), rien n'empêche un contributeur autorisé — ou une source de contenu importée automatiquement — d'y placer une balise `script` ou un gestionnaire d'événement dans un attribut.

## Corriger sans renoncer au HTML enrichi

La première option, la plus sûre, consiste à ne jamais utiliser de champ texte libre pour du contenu destiné à devenir du HTML. Les composants `RichText` de `@wordpress/block-editor` gèrent déjà leur propre sérialisation et échappent correctement le contenu qu'ils produisent ; il est presque toujours préférable de les utiliser plutôt que de reconstruire du HTML à la main.

> L'essentiel à retenir : Ne jamais injecter une valeur d'attribut brute dans save() ; Filtrer côté serveur en complément du filtrage côté client ; Tester avec des caractères hostiles avant publication

Quand `dangerouslySetInnerHTML` reste nécessaire — par exemple pour afficher un extrait provenant d'une source externe déjà en HTML — la valeur doit être filtrée avant d'être stockée dans les attributs du bloc, pas seulement au moment de l'affichage.

## Filtrer côté client avant la sauvegarde

La bibliothèque `@wordpress/dom-purify` ou une fonction de nettoyage maison peuvent être appliquées dans l'éditeur, avant que la valeur ne devienne un attribut du bloc :

```
import { safeHTML } from '@wordpress/dom';

function onChangeExtrait( html ) {
  setAttributes( { extrait: safeHTML( html ) } );
}
```

`safeHTML()`, exposée par `@wordpress/dom`, retire les éléments et attributs dangereux d'une chaîne HTML côté client. Elle n'est pas un filtre exhaustif au niveau serveur, mais elle réduit fortement la surface d'attaque au moment de la saisie.

## Ne jamais faire confiance uniquement au filtrage client

Un filtrage exécuté uniquement dans le navigateur reste contournable : un attaquant qui appelle directement l'API REST des articles, ou qui importe du contenu via un flux WXR, n'exécute jamais le code JavaScript de l'éditeur. C'est pourquoi un filtre serveur reste nécessaire, appliqué au moment de la sauvegarde du post :

```
add_filter( 'content_save_pre', function ( $content ) {
    return wp_kses_post( $content );
} );
```

Ce filtre s'applique au contenu global du post, y compris le HTML statique produit par `save()`, et retire les balises et attributs non autorisés par la liste par défaut de `wp_kses_post()`.

## Tester avec des charges hostiles avant publication

Avant de valider un bloc qui manipule du HTML dynamique, il vaut mieux le tester volontairement avec des valeurs conçues pour révéler une faille : une chaîne contenant `<img src=x onerror=alert(1)>`, un attribut `href` en `javascript:`, ou une balise `svg` avec un gestionnaire embarqué. Si l'un de ces éléments survit à l'enregistrement du post et apparaît côté public, le filtrage est insuffisant.

- Tester une charge dans un champ texte simple, pas seulement dans un RichText
- Vérifier le HTML réellement enregistré en base, pas seulement l'aperçu dans l'éditeur
- Rejouer le test après chaque modification du bloc, un correctif partiel pouvant réintroduire le problème ailleurs

## Ce qu'il faut retenir

Utiliser `dangerouslySetInnerHTML` dans `save()` n'est pas une erreur en soi ; l'erreur consiste à l'alimenter avec une valeur qui n'a jamais été filtrée, ni côté client au moment de la saisie, ni côté serveur au moment de l'enregistrement. Préférer les composants `RichText` quand c'est possible, et combiner un nettoyage client avec un filtre serveur systématique, ferme la quasi-totalité des scénarios d'injection observés sur ce type de bloc.
