Le WordPress d'aujourd'hui, décodé pour les développeurs

IA & MCP

Stocker une clé API de LLM en sécurité plutôt que dans un champ ACF

Snippet de stockage chiffré d'une clé API de LLM en base de données, à l'abri d'un champ personnalisé visible depuis l'éditeur. Sans traiter la rotation automatique de la clé.

Par WordPress Développement • 15 juillet 2023 • 4 min de lecture • Aucun commentaire
Stocker une clé API de LLM en sécurité plutôt que dans un champ ACF

openssl_encrypt() : cette seule fonction PHP change la donne pour qui stocke une clé API de fournisseur de LLM dans WordPress. Trop de projets la placent encore dans un champ ACF de type texte, visible en clair dans l’éditeur par n’importe quel rédacteur ayant accès à la page de réglages, et parfois même exporté sans y penser dans un export JSON de contenu.

Ce billet propose un snippet concret pour chiffrer la clé avant stockage en base et la déchiffrer uniquement au moment de l’appel serveur. La rotation automatique de cette clé, sujet à part entière, n’est pas traitée ici.

Le problème du champ ACF en clair

Un champ personnalisé standard, qu’il soit ACF ou un simple add_option(), stocke sa valeur telle quelle dans la table wp_options ou wp_postmeta. N’importe quel utilisateur avec un accès administrateur, ou un plugin mal intentionné doté des bonnes capacités, peut lire cette valeur directement en base ou via l’API REST si le champ est exposé par erreur. Une clé API de LLM qui fuite de cette manière expose directement le budget de génération à un usage frauduleux.

Le risque ne se limite pas à une lecture malveillante : un export de contenu au format JSON, une sauvegarde de base non chiffrée transmise à un prestataire, ou une capture d’écran de l’éditeur suffisent à divulguer la clé sans qu’aucune intrusion ne soit nécessaire.

Le principe du chiffrement au repos

L'essentiel à retenir : Un champ ACF classique expose la clé à tout utilisateur avec accès à l'éditeur ; Le chiffrement avec OpenSSL protège la valeur stockée en base ; La clé en clair ne doit jamais transiter côté navigateur

La clé n’est jamais stockée en clair. Elle est chiffrée avec openssl_encrypt() en AES-256-CBC, à l’aide d’une clé de chiffrement dérivée d’une constante définie dans wp-config.php — jamais dans la base de données elle-même. Un vecteur d’initialisation de 16 octets, différent à chaque chiffrement, accompagne la valeur stockée.

// Dans wp-config.php, hors du dépôt versionné
define( 'LLM_KEY_SECRET', 'une-chaine-aleatoire-de-64-caracteres' );

function chiffrer_cle_llm( $cle_en_clair ) {
    $iv = openssl_random_pseudo_bytes( 16 );
    $chiffre = openssl_encrypt(
        $cle_en_clair,
        'aes-256-cbc',
        LLM_KEY_SECRET,
        0,
        $iv
    );
    return base64_encode( $iv . '::' . $chiffre );
}

function dechiffrer_cle_llm( $valeur_stockee ) {
    list( $iv, $chiffre ) = explode( '::', base64_decode( $valeur_stockee ), 2 );
    return openssl_decrypt( $chiffre, 'aes-256-cbc', LLM_KEY_SECRET, 0, $iv );
}

La clé chiffrée est ensuite stockée via update_option( 'llm_api_key_chiffree', chiffrer_cle_llm( $cle ) ), jamais dans un champ visible dans l’éditeur de contenu.

Le déchiffrement uniquement côté serveur

Le déchiffrement ne doit avoir lieu que dans le contexte d’un appel serveur vers le fournisseur de LLM, jamais pour affichage dans une page d’administration :

function appeler_llm( $prompt ) {
    $cle = dechiffrer_cle_llm( get_option( 'llm_api_key_chiffree' ) );

    return wp_remote_post( 'https://api.fournisseur-llm.example/v1/completions', array(
        'headers' => array(
            'Authorization' => 'Bearer ' . $cle,
            'Content-Type'  => 'application/json',
        ),
        'body' => wp_json_encode( array( 'prompt' => $prompt ) ),
        'timeout' => 20,
    ) );
}

Les précautions complémentaires

  • Ne jamais journaliser la clé en clair, même dans des logs de debug temporaires.
  • Restreindre l’accès à l’option chiffrée via register_setting() avec une capacité stricte comme manage_options.
  • Exclure wp-config.php du dépôt Git et de toute sauvegarde transmise sans chiffrement.

Ce que ce snippet ne résout pas

Le chiffrement au repos protège contre une lecture directe en base, mais pas contre un serveur compromis où un attaquant a déjà accès au code source et donc à wp-config.php. Dans ce scénario, seule une rotation régulière de la clé, couplée à une surveillance de l’usage via le tableau de bord du fournisseur, limite réellement les dégâts.

Chiffrer une clé qu’on ne fait jamais tourner ne fait que retarder le problème : le chiffrement protège le stockage, pas l’usage qui en est fait une fois la clé compromise ailleurs.

En résumé

Un champ personnalisé, aussi pratique soit-il pour un rédacteur, n’a jamais été conçu pour protéger un secret. Le couple openssl_encrypt() / openssl_decrypt(), associé à une clé de chiffrement hors base de données, reste la méthode la plus simple pour retirer une clé API de LLM du champ de vision de qui n’en a pas besoin.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi