Une clé passée à wp_set_secret reste-t-elle en clair ?
1 min de lecture
Une clé passée à wp_set_secret reste-t-elle en clair ?
Non. Depuis le 13 octobre 2026, wp_set_secret() enregistre le secret chiffré. Lire la ligne dans wp_options ne rend pas la clé d’API. WordPress 7.2 n’est pas encore stable : la bêta 4, publiée le 3 novembre 2026, sert à tester. La sortie prévue est le 8 décembre. Un site en ligne reste sur la branche 7.1.
Ce que la bêta 4 contient vraiment
Le 3 novembre 2026, David Baumwald a annoncé WordPress 7.2 bêta 4. L’annonce interdit de l’installer sur un site en production. La bêta 3 n’a pas eu de billet à part : elle est partie le 28 octobre avec WordPress 7.1.4, version de la branche stable. Le calendrier sur Make WordPress Core place la bêta 5 le 10 novembre, la RC 1 le 17 novembre et la finale le 8 décembre 2026. Jusqu’à la RC 1, un nom ou un texte de Santé du site peut encore changer.
Le fichier src/wp-includes/secrets.php du tronc, relu le 4 novembre, n’a plus bougé depuis le commit 5744bc5803 du 13 octobre, « Security: Introduce the Secrets API. ». La bêta 4, coupée après, embarque cette API. Sur un 7.1.4, function_exists( 'wp_set_secret' ) vaut false. Une clé collée dans l’écran d’un plugin reste alors, le plus souvent, en clair. Le cœur ne devine pas quelle option est un secret, et ne les convertit pas tout seul.
Où va la chaîne passée à wp_set_secret
wp_set_secret() prend un nom et une chaîne, la chiffre, puis la range. Il n’y a pas de mode en clair, ni de constante pour couper le chiffrement. Le fournisseur livré, WP_Secrets_Libsodium_Provider, appelle sodium_crypto_aead_xchacha20poly1305_ietf_encrypt : du XChaCha20-Poly1305. Si ni libsodium ni sodium_compat n’est là, l’appel renvoie secret_crypto_unavailable et n’écrit pas la clé telle quelle.
La ligne se nomme _wp_secret_ suivi du nom, par exemple _wp_secret_boutique/stripe-secret. L’autoload est désactivé : WordPress ne la charge pas à chaque requête. Le réseau utilise _wp_network_secret_. Sur un site simple, les deux préfixes sont dans wp_options. Sur un multisite, le réseau va dans wp_sitemeta. Aucun des deux ne retombe sur l’autre.
Le tableau stocké n’a pas le texte. L’emplacement courant porte ct, le chiffré, dk, la clé de donnée elle-même chiffrée, deux nonces, une empreinte, une date et le drapeau needs_rotation. Chercher la clé Stripe dans option_value ne la trouve pas. Le nom, lui, est dans option_name. Un nom de secret n’est pas un secret.
wp_set_secret( 'boutique/stripe-secret', $key );
$secret = wp_get_secret( 'boutique/stripe-secret' );
if ( $secret instanceof WP_Secret ) {
$key = $secret->reveal();
} elseif ( null === $secret ) {
// Jamais enregistré.
} else {
// WP_Error : la ligne existe, le déchiffrement a échoué.
}
Les trois retours sont séparés à dessein. null veut dire absent. WP_Error, souvent secret_key_unavailable, veut dire présent mais illisible. Les confondre rouvrirait l’écran de branchement d’un plugin alors que la clé de site a changé. Avec le fournisseur livré, un objet WP_Secret a déjà été déchiffré : reveal() renvoie la chaîne. La signature accepte aussi une erreur, pour un fournisseur qui prouve l’existence du secret sans le donner à PHP. Ce cas ne se produit pas avec le coffre par défaut.
La clé qui ouvre le coffre reste hors de la base
La clé de site n’est jamais écrite. Si WP_SECRETS_KEY est le base64 canonique de 32 octets, ces octets servent tels quels. Sinon la constante est passée à sodium_crypto_generichash, un BLAKE2b. Sans constante, le même hash est calculé sur LOGGED_IN_KEY collé à LOGGED_IN_SALT. La phrase d’exemple de wp-config-sample.php, « put your unique phrase here », est refusée. L’API ne reprend pas les copies de sels gardées en base.
Cette clé de site enveloppe une clé racine, stockée sous _wp_secrets_root_key. Ouverte en mémoire, la clé racine dérive une clé maître par site, via sodium_crypto_kdf_derive_from_key. La clé maître enveloppe une clé de donnée tirée au hasard à chaque écriture, et c’est elle qui chiffre la valeur. Un dump SQL seul donne le chiffré et la clé racine encore fermée. Ouvrir le secret demande aussi wp-config.php. Une archive qui réunit les deux contient de quoi tout lire.
Ajouter WP_SECRETS_KEY après le premier secret, ou changer les sels quand la constante n’existe pas, change la clé de site. La clé racine déjà envelopée ne s’ouvre plus. Lecture et écriture renvoient secret_key_unavailable. Rien n’est recopié en clair pour « sauver » le site. WP_Secrets_Key_Manager::rotate_site_key() et WP_SECRETS_KEY_PREVIOUS savent réenvelopper la clé racine sans toucher aux secrets, mais secrets.php ne publie pas de fonction wp_* pour ça, et aucun écran ne le fait. La constante se pose avant la première écriture.
php -r 'echo base64_encode( random_bytes( 32 ) ), PHP_EOL;'
Le résultat se place dans wp-config.php : define( 'WP_SECRETS_KEY', '…' );. Santé du site cite wp secret generate-key quand la forme n’est pas celle-là. Cette commande n’est pas dans l’arbre du cœur relu le 4 novembre. La ligne PHP produit les 32 octets reconnus. Une phrase libre est acceptée, puis marquée « recommended ». Une fois des secrets écrits, poser la constante ensuite casse la lecture.
Santé du site ajoute trois tests : la source de la clé, les secrets indéchiffrables, ceux qui attendent une rotation. Un secret illisible est critique. Le texte le dit : la valeur d’origine ne se récupère pas, il faut la ressaisir. L’onglet Informations donne le fournisseur et la source de clé, sans valeur et sans empreinte.
Ce que l’objet masque, et ce qu’un plugin lit encore
wp_get_secret() ne renvoie pas une chaîne. Le texte vit dans une table statique, pas dans une propriété. echo, json_encode() et var_dump() affichent [secret:boutique/stripe-secret]. serialize() et clone lèvent LogicException. print_r() ne voit pas le texte non plus. Il peut montrer le nom et l’empreinte.
L’empreinte fait 32 caractères hexadécimaux. C’est un BLAKE2b de 16 octets, lié à la clé maître : le même secret n’a pas la même empreinte sur deux sites. Le champ enregistré n’est pas authentifié par le chiffre. On le recalcule après reveal() et on compare avec hash_equals(). Aucun filtre ne passe sur la lecture. Le seul hook, wp_secret_changed, part à l’écriture, à l’import, à la suppression et au retrait. Il reçoit des empreintes, jamais la valeur.
Les fonctions ne vérifient pas de capacité : un cron n’a pas toujours d’utilisateur. manage_secrets va à l’administrateur seul. Elle vise un écran repoussé à la 7.3, pas wp_get_secret(). Un plugin qui exécute du PHP lit le secret.
Le nom a la forme extension/nom-secret : minuscules, chiffres, tirets, underscores, un seul slash, 172 caractères au plus. API-Key renvoie secret_invalid_name. Un nom sans slash passe, avec un _doing_it_wrong(). Deux extensions qui choisissent api-key s’écrasent. Le préfixe range. Il n’isole pas.
Ce que le chiffrement ne répare pas
wp_import_option_as_secret() copie une option textuelle et la laisse en place. Le secret reçoit needs_rotation : les dumps déjà partis contiennent encore le clair. Après une comparaison avec hash_equals(), le plugin supprime l’option. Une option absente ou non textuelle renvoie secret_invalid_value. Il n’y a que deux emplacements, CURRENT et PREVIOUS. wp_retire_secret_version() retire l’ancien. Rien ne le fait à sa place.
L’écran Toutes les options, wp-admin/options.php, ne saute pas ces lignes, malgré le commentaire de WP_Secrets_Option_Store. Le 4 novembre 2026, la boucle affiche encore chaque option. Un tableau sérialisé y paraît comme « SERIALIZED DATA », non éditable. Le nom _wp_secret_… reste lisible. /wp/v2/settings ne liste que les réglages enregistrés : un secret n’y est pas, sans filtre spécial.
Un wp-content/secrets.php peut remplacer le stockage ou le trousseau. S’il est cassé, le cœur ne retombe pas sur les options : chaque appel échoue. wp_secrets_memzero() n’efface la mémoire que si l’extension sodium est présente, et seulement la variable reçue. Sous sodium_compat, la variable locale est vidée, la chaîne en mémoire ne l’est pas.
Ce qu’on peut préparer sans installer la bêta
La bêta se teste sur une copie : extension WordPress Beta Tester, canal « Bleeding edge », flux « Beta/RC Only », ou wp core update --version=7.2-beta4. Pas sur le site qui encaisse.
D’ici le 8 décembre, le travail utile est de lister les options qui contiennent une clé, et d’écrire un lecteur qui n’appelle l’API que si la fonction existe. Les trois retours restent distincts. Une erreur ne doit pas relire l’ancienne option en clair. Une constante dans wp-config.php reste le premier choix quand on peut éditer ce fichier : elle ne passe pas par la base. Sur la copie de test, WP_SECRETS_KEY se définit avant le premier wp_set_secret(). Après une migration, la clé chez le prestataire se renouvelle. L’ancienne est déjà dans d’anciennes sauvegardes.
Questions fréquentes
WordPress 7.1.4 a-t-il déjà wp_set_secret ?
Non. La fonction est dans la branche 7.2, commitée le 13 octobre 2026 et présente dans la bêta 4 du 3 novembre. WordPress 7.1.4, sorti le 28 octobre, est la branche stable. Il a transporté la bêta 3 pour les testeurs. Il n’active pas l’API.
La bêta 4 protège-t-elle les clés déjà en base ?
Non, et elle ne s’installe pas sur un site en production. Même après le 8 décembre, une option existante reste en clair tant qu’un plugin n’a pas appelé wp_import_option_as_secret() puis supprimé l’option. L’import pose needs_rotation : les anciennes sauvegardes gardent l’ancienne clé.
Qui peut encore lire le secret ?
Tout PHP du site qui appelle wp_get_secret() puis reveal(). manage_secrets n’est pas vérifiée dans la fonction. Un dump SQL sans wp-config.php ne donne pas le texte. Une sauvegarde qui contient les deux, si.
Régénérer les sels casse-t-il les secrets ?
Oui, tant que WP_SECRETS_KEY n’est pas définie : la clé de site vient alors de LOGGED_IN_KEY et LOGGED_IN_SALT. Santé du site signale les secrets illisibles. Il faut les ressaisir. La constante, posée avant la première écriture, détache le coffre de cette rotation.
L’écran Toutes les options affiche-t-il la clé ?
Il affiche le nom _wp_secret_… et le libellé « SERIALIZED DATA », pas le texte. Le commentaire de classe qui annonce une exclusion de options.php ne correspond pas à la boucle relue le 4 novembre 2026.
Les noms et les retours se revérifient dans le tronc : ils peuvent encore bouger avant la RC 1 du 17 novembre. Pour la progression, les parcours HTML, CSS, Sass, JavaScript et TypeScript sont sur la page Apprentissage. Cet article n’en reprend aucun chapitre.
Voir Egalement
My Blog
Un message ChatGPT entame-t-il le quota Codex en décembre ?
9 min de lecture À partir de décembre 2026, OpenAI annonce une enveloppe commune, comptée en tokens, pour ChatGPT et Codex sur les forfaits Plus, Pro et Business. Le chat Instant n’y puise pas. La page d’aide ne donne ni le jour du mois, ni un nombre de tokens. Read more
Firefox 159 : animation-timeline remplace-t-il un écouteur de scroll ?
9 min de lecture Le 27 octobre 2026, Firefox 159 active les animations pilotées par le défilement. animation-timeline suffit pour une barre de lecture ou l’entrée d’une image, pas pour un écouteur qui charge des données. Read more