June 20264 min de lectureJohan Bretonneau

On s'est fait prendre à fuiter un secret, et la passerelle a dit non
Pourquoi masquer les secrets dans votre app est nécessaire mais jamais suffisant

Notre propre agent d'infrastructure a tenté d'envoyer un mot de passe à un LLM. Le Security Shield de notre propre passerelle l'a bloqué avant qu'il ne quitte le réseau. Voici l'incident, pourquoi l'étape de masquage l'a raté, et pourquoi le scan de secrets doit vivre au niveau de la passerelle LLM.

On fait tourner un agent IA interne qui diagnostique notre propre infrastructure. Quand un conteneur déraille, il ramasse les preuves (la définition du service, les dernières lignes de logs), les envoie à un LLM, et propose une analyse de cause racine. Utile, banal, exactement le genre de tâche de plomberie où les agents excellent.

Comme toute équipe sérieuse, on masque les secrets avant que quoi que ce soit ne quitte le périmètre. L'agent passe le bundle dans une étape de nettoyage qui retire mots de passe, clés d'API, tokens et chaînes de connexion. Ceinture et bretelles.

Puis un matin, notre canal d'alertes s'allume :

🚨 security_shield: BLOCKED  threat=secrets  score=0.95

Notre propre passerelle LLM venait de refuser une requête de notre propre agent. Elle avait trouvé un secret dans un payload que notre étape de masquage avait pourtant déjà « nettoyé ».

C'est à la fois la pire et la meilleure nouvelle qu'on puisse recevoir.

La pire nouvelle : notre masquage avait un trou

L'étape de nettoyage, c'était ce que presque toutes les équipes livrent en premier : une denylist artisanale. Un jeu de regex qui cherche des noms de variables comme password, secret, token, api_key, plus une poignée de formats de tokens connus (sk-…, JWT, mots de passe dans les chaînes de connexion). Ça marche, jusqu'à ce que ça ne marche plus.

Voici une version anonymisée de ce qui est passé à travers, reproduite contre le vrai code de masquage :

# ce que l'agent a collecté (anonymisé)
POSTGRES_PASSWORD: ********              <- capturé (nom contient "password")
GOOGLE_API_KEY:    ********              <- capturé (nom contient "api_key")
conn=postgres://app:********@db:5432     <- capturé (règle chaîne de connexion)

DB_PASS:  pg_h0tfix_9921xQ              <- FUITÉ ("pass" != "password")
pwd=hunter2hunter2                       <- FUITÉ (la liste avait "password|passwd", pas "pwd")
Authorization: Bearer abk_live_9Hk29Lm…  <- FUITÉ (seuls certains formats de clé couverts)

Trois secrets différents sont passés tout droit à travers le masquage :

  • un mot de passe sous une variable nommée DB_PASS, parce que la denylist matchait password, pas pass ;
  • une ligne pwd=, parce que la liste avait password et passwd, mais personne n'avait pensé à pwd ;
  • un token Bearer à préfixe non standard, parce que le masqueur ne connaissait que quelques formats de clé précis.

Aucun n'est exotique. C'est le vocabulaire quotidien de vrais fichiers de conf et de vrais logs. Une denylist, c'est la liste des secrets dont vous vous êtes souvenu. La prod est pleine de ceux que vous avez oubliés.

La bonne nouvelle : la passerelle a tout rattrapé quand même

Chaque requête de notre agent passe par notre passerelle LLM, et la passerelle exécute un Security Shield à l'entrée. Le Shield se moque de savoir quelle app, quel SDK, quel agent ou quel modèle est en jeu. Il scanne le contenu de chaque requête à la recherche de secrets, de données personnelles et d'injection de prompt, avant que la requête ne soit jamais transmise à un fournisseur.

Son détecteur de secrets a rattrapé exactement ce que notre masquage avait raté :

HIT  bearer\s+[A-Za-z0-9\-._~+/]{20,}      -> "Bearer abk_live_9Hk29Lm…"
HIT  (password|passwd|pwd)\s*[:=]\s*\S{8,} -> "pwd=hunter2hunter2"
-> threat=secrets, score=0.95 -> requête rejetée (HTTP 400)

Le mot de passe n'a jamais atteint le modèle. Il n'a jamais atteint le fournisseur. Il n'a jamais quitté notre réseau. Le Shield a transformé une fuite de données silencieuse en un 400 bien bruyant et inoffensif, plus un ping Slack.

La leçon : le masquage est un contrôle, la passerelle est le filet

C'est tentant de lire ça comme « notre masquage était mauvais, corrigeons la regex ». On a corrigé la regex. Mais ça passe à côté de l'essentiel.

Vous ne finirez jamais la denylist. Demain c'est un webhook Slack, une clé AWS AKIA…, un token GitHub ghp_…, un format Bearer propre à un fournisseur que vous n'avez jamais vu. Chaque nouveau service ajoute une nouvelle forme de secret. Une étape de masquage dans une seule app ne connaît que les motifs dont une seule équipe s'est souvenue, le jour où elle l'a écrite.

Un scanner de secrets au niveau de la passerelle est structurellement différent :

  • Il voit chaque requête. Un seul point d'application couvre chaque app, chaque agent, chaque équipe, chaque modèle, y compris le script expérimental que quelqu'un a bricolé à 2 h du matin sans aucun masquage.
  • Il est centralisé. Ajoutez un motif une fois, et tous les appelants sont protégés. Pas besoin de redéployer dix services.
  • Il est la couche par où les secrets passent vraiment. Le masquage de votre app est optionnel et facile à oublier. La passerelle, c'est la route ; rien n'atteint un fournisseur sans l'emprunter.
  • Il est rapide et à plusieurs étages. Une passe déterministe par regex tourne en moins de deux millisecondes sur chaque requête ; une passe optionnelle par LLM n'escalade que sur les requêtes suspectes, vous obtenez la couverture sans payer la latence sur les 99 % qui sont propres.

Et voici la partie honnête, parce que l'honnêteté est la seule chose qui rend une histoire de sécurité digne d'être lue : aucune couche n'est parfaite. Dans notre incident, un secret a filé entre à la fois le masquage et l'étage regex rapide du Shield. Ce n'est pas un argument contre le Shield ; c'est tout l'argument pour l'empilement. Masquage dans l'app, scan de secrets à la passerelle, moindre privilège sur les secrets eux-mêmes, identifiants à durée de vie courte. Chaque couche est un filtre troué ; on les empile pour que les trous ne s'alignent pas.

C'est ça, la défense en profondeur. Le masquage est un contrôle. La passerelle est le filet. Vous voulez les deux, et vous voulez surtout que le filet soit la seule chose que chaque requête est obligée de traverser.

On lui fait assez confiance pour garder notre propre infra

Si on peut raconter cette histoire sans rougir, c'est que l'agent qui s'est fait bloquer est le nôtre, la passerelle qui l'a bloqué est la nôtre, et on l'a appris par nos propres alertes, pas par un client, pas par un rapport de fuite.

C'est ce que fait le Security Shield de HiWay2LLM sur chaque requête qui traverse la passerelle : il scanne les secrets fuités, les données personnelles et les tentatives d'injection avant que l'appel ne soit jamais émis, avec un chemin rapide déterministe et un scan profond optionnel par LLM, et il envoie les événements de menace directement vers votre SIEM. On pointe nos propres agents d'infrastructure dessus pour exactement la raison de ce billet, parce que l'endroit où arrêter un secret, c'est le seul goulot que chaque requête doit franchir.

Si vos agents et vos apps parlent à des LLM par des chemins que vous ne maîtrisez pas à 100 % (et aujourd'hui, qui les maîtrise ?), mettez un filet sur la route qu'ils sont tous obligés d'emprunter.

Économiser maintenant →

Aucune carte bancaire requise

Partager

LinkedInXEmail

Cet article t'a servi ?

Commentaires

Sois le premier à commenter.