Protection des secrets

Protéger les clés API dans le prompt : comment cloak les masque

Pourquoi clés et mots de passe finissent dans les prompts IA, ce que cela coûte, et comment cloak les remplace par des jetons avant l’envoi. Avec preuves.

UltraNOS16 min de lecture

Une clé API n’arrive pas dans le prompt parce que quelqu’un l’y a tapée. Elle y arrive parce que vous copiez un message d’erreur avec son environnement, parce qu’un agent de code exécute read_file sur le .env, parce qu’une commande de terminal affiche env, ou parce qu’un journal enregistre l’en-tête Authorization. À partir de là, le prompt est une donnée stockée chez le fournisseur du modèle. Sa durée de conservation relève des conditions de ce dernier — et un opt-out de l’entraînement n’est pas une suppression : c’est seulement l’engagement de ne pas utiliser la donnée pour entraîner. La voie fiable n’est donc pas de faire attention, mais de remplacer : les secrets sont échangés contre des jetons avant l’envoi. C’est exactement ce que fait cloak dans UltraNOS, et cet article décrit le chemin étape par étape, y compris les endroits où il présente encore des lacunes.

Pourquoi « je fais attention » ne suffit pas

Tant que vous écrivez chaque prompt vous-même, vous pouvez retirer les clés avant l’envoi. Un agent ne le fait pas. Il lit des fichiers, exécute des commandes et renvoie les résultats au modèle au tour suivant — sans que vous les voyiez avant. Trois chemins concrets, tous tirés du matériau source du dépôt :

  1. Résultats d’outils. Dans le protocole Anthropic, tool_result.content est l’endroit où arrivent les sorties du shell, le contenu des fichiers et la sortie de env. L’implémentation de cloak l’appelle, dans cloak/cloak/transform/wire_anthropic.py, la « source la plus productive ». Un agent qui lit une configuration pour comprendre une erreur l’envoie en entier au modèle.
  2. Complétion dans l’éditeur. Les suggestions en ligne partent vers le modèle avec le contenu du fichier environnant. Dans UltraNOS, ce chemin est resté non protégé jusqu’au 19 août 2026 ; le correctif se trouve dans backend/api/completions_routes.py. Si vous avez un .env ouvert dans l’éditeur, la clé est dans le contexte.
  3. Documentation et dépôts. Lors de la campagne de mesure des règles de détection sur le dépôt lui-même (commit 27a5558), tous les résultats étaient de vrais secrets, zéro fausse alerte — et ils se trouvaient dans des fichiers de projet qu’une session IA lit automatiquement, pas dans un coin perdu.

L’observation de Truffle Security, selon laquelle les modèles guident les développeurs vers le codage en dur des clés (trufflesecurity.com/blog), va dans le même sens : le prompt n’est pas un cas particulier, c’est la voie normale par laquelle les secrets quittent la machine. Le Top 10 OWASP pour les applications LLM fait de la « Sensitive Information Disclosure » un point à part entière, et pour de bonnes raisons.

Ce que fait cloak — étape par étape

cloak est un paquet Python sans dépendance d’exécution qui intervient dans le processus du backend UltraNOS, juste avant l’envoi (backend/privacy/cloak_gate.py). Pas de proxy intermédiaire, pas de seconde connexion : le corps de la requête est analysé, remplacé, puis envoyé.

Détection

Cinq catégories sont remplacées : secret, secret_block, credential, email et person (cloak/cloak/types.py). Toutes sont actives par défaut. La détection s’exécute comme un pipeline de huit étapes fixes, dont l’ordre détermine la priorité (cloak/cloak/detect/engine.py) :

Étape Ce qu’elle fait
0 reconnaître les jetons existants, ne pas les remplacer deux fois
1 liste d’autorisation : réserver clés publiques, empreintes (digests), URI data:, correspondances de ports, etc.
2 blocs de clés (PEM, PuTTY)
3 préfixes de fournisseurs (sk-ant-…, ghp_…, AKIA…, etc.)
4 identifiants structurels (KEY=valeur, Authorization:, chaînes de connexion, JWT, .netrc, curl -u)
5 valeurs du coffre que vous avez vous-même enregistrées
6 adresses e-mail
7 formes sans contexte (mysql -p, redis-cli -a, --password, jetons opaques à partir d’une entropie de 4,0)

En chiffres, comptés par import le 7 septembre 2026 : 27 règles de préfixe, 13 règles structurelles, 2 règles de bloc et 8 réservations de liste d’autorisation dans cloak/cloak/detect/patterns.py, plus 6 règles dans detect/unbekannt.py et la règle e-mail dans detect/pii.py. Total : 57 spécifications de règles, dont 49 remplaçantes et 8 réservantes, plus le détecteur de coffre pour les valeurs connues.

Chaque règle est une expression régulière à quantificateurs bornés — pas de .*, comme frein contre le ReDoS — et possède quatre garde-fous : longueur minimale et maximale, une liste de rejet et un seuil d’entropie (detect/rules.py). L’entropie est exprimée en bits de Shannon par caractère et ne s’applique qu’après une ancre de contexte ; les seuils vont de 2,0 pour les affectations KEY=valeur à 4,0 pour les jetons opaques. Les listes de rejet écartent les exemples de documentation placés derrière un vrai préfixe, les simples marqueurs comme <token> ou $VAR, les mots de passe faibles et les références de code. En cas de chevauchement, c’est la catégorie la plus digne de protection qui l’emporte, pas la correspondance la plus longue (cloak/cloak/spans.py).

Avec KEY=valeur, seule la valeur est d’ailleurs remplacée. Le modèle voit ANTHROPIC_API_KEY=CLOAK_SECRET_… et sait donc toujours de quoi il s’agit.

Jetons de remplacement

Un jeton a la forme CLOAK_<CATÉGORIE>_<12 hex>, soit CLOAK_SECRET_…, CLOAK_CREDENTIAL_…, CLOAK_EMAIL_… et ainsi de suite ; le plus long fait 31 caractères (cloak/cloak/placeholder.py). L’identifiant est aléatoire, issu de secrets.token_hex, et non un hachage de la valeur — on ne peut rien déduire du jeton.

Il reste pourtant stable : quand la même valeur réapparaît, le coffre cherche l’entrée existante par un HMAC d’index sur poivre, catégorie et valeur, et émet à nouveau le même jeton (cloak/vault/store.py). Cela vaut pour tout le processus et d’une session à l’autre. Pour le modèle, cela signifie que la même clé porte le même nom au tour 1 et au tour 12 : il peut donc l’utiliser de façon cohérente dans ses commandes.

Le modèle reçoit en outre une courte note en anglais, [cloak] Privacy layer active …, avec trois règles — uniquement dans le profil enforce, et uniquement si le corps contient au moins un jeton (cloak/cloak/transform/notice.py).

Substitution inverse lors des appels d’outils

Le vrai tour de force se trouve derrière le modèle. Quand il renvoie un appel d’outil contenant un jeton, par exemple curl -H "Authorization: Bearer CLOAK_SECRET_…", UltraNOS résout le jeton juste avant l’exécution (sitzung.werkzeug() dans cloak_gate.py). L’outil reçoit la vraie valeur. L’historique qui repart vers le modèle conserve le jeton. Et le résultat de l’outil est de nouveau entièrement masqué à chaque tour — même si le modèle vient de lire de nouveaux secrets avec read_file ou Bash, pourvu qu’un motif corresponde.

La traduction inverse s’alimente exclusivement du registre (ledger) de ce tour. Un registre par tour d’utilisateur, conservé sur tous les tours d’outils ; un jeton issu d’une autre session n’est pas résolu dans ce tour (cloak/cloak/engine.py).

Pour la réponse qui vous est destinée : le flux est retraduit, avec une mémoire tampon d’au plus 32 caractères aux frontières de segments, afin qu’un jeton ne passe pas à travers la jointure de deux segments (cloak/cloak/streaming.py). Pour un humain, les secrets restent des jetons dans le texte affiché (reveal=false), tandis que les adresses e-mail et les personnes réapparaissent en clair.

Fail-closed : que se passe-t-il quand la protection n’est pas prête

Le réglage par défaut est le plus contraignant : si l’indicateur de fonctionnalité ULTRANOS_FEATURE_CLOAK est activé et que le paquet est absent ou que l’exécution ne s’ouvre pas, la porte lève CloakNichtVerfuegbar et le tour échoue de façon lisible, au lieu d’envoyer du texte en clair (cloak_gate.py). Aucun fournisseur n’intercepte cette exception ; sur le chemin CLI, la CLI n’est même pas lancée (llm/test_cloak_cli.py). Dans ce cas, la complétion de l’éditeur ne renvoie aucune suggestion plutôt qu’une suggestion non masquée.

C’est le bon réglage par défaut, car l’alternative — laisser passer en silence — reproduit exactement l’état du 19 août 2026 au matin : une version préliminaire du paquet avait audit comme profil par défaut, comptait les correspondances et transmettait le texte à l’identique avec un HTTP 200. Le journal des modifications du dépôt appelle cela « pire que pas de proxy », parce que cela suggère une protection là où il n’y en a aucune. Depuis le 19 août 2026, enforce est le réglage par défaut, même sans fichier de configuration (cloak/cloak/config.py). En enforce, un nœud de plus de 1 Mio déclenche LimitExceeded, et une exception dans le détecteur est propagée au lieu d’être avalée. Les clés de configuration inconnues sont des erreurs ; les droits d’accès au fichier du coffre sont vérifiés, pas réparés en silence.

Sur tous les chemins — avec les dates

cloak ne se trouve pas à un seul endroit, mais devant chaque fournisseur qu’UltraNOS connaît. L’ordre dans lequel les chemins ont été couverts figure dans le journal des modifications :

  • 19 août 2026 : protocoles Anthropic et OpenAI (backend/llm/anthropic_provider.py, openai_provider.py, tours d’outils compris), complétion dans l’éditeur, mise en production active. Les fournisseurs personnalisés utilisant le protocole OpenAI ou Anthropic héritent de la protection.
  • 24 août 2026 : Ollama, dernier fournisseur HTTP rattrapé (commit fbd6960).
  • 31 août 2026 : les chemins CLI claude-cli et claude-instanz, c’est-à-dire Claude Code comme fournisseur (commit fbd3d70, 17 tests).
  • 3 septembre 2026 : règles de préfixe pour nano-gpt et Requesty (commit 31761b8).

Sur le protocole Anthropic, sont analysés le prompt système, chaque bloc de contenu des messages — texte, tool_use.input, tool_result.content —, les descriptions d’outils et les métadonnées. Sur le protocole OpenAI : les contenus de tous les rôles, y compris role="tool", les arguments des appels d’outils sous forme de JSON analysé et les descriptions d’outils (wire_openai.py). Le prompt système est donc inclus : une clé figurant dans une note de projet jointe en tête comme contexte est remplacée exactement comme une clé tapée dans le chat.

Vérifier plutôt que croire

La seule preuve fiable aujourd’hui est GET /api/health (détails sur l’API sous /#developer). Le champ cloak connaît trois états : {aktiv:false}, {aktiv:true, bereit:false, fehler} et l’état complet. Mesuré le 7 septembre 2026 sur l’instance hébergée :

"cloak": {
  "aktiv": true,
  "bereit": true,
  "profil": "enforce",
  "kategorien": ["credential", "email", "person", "secret", "secret_block"],
  "ersetzt": true
}

bereit:false est aussi l’état « pas encore ouvert » — l’exécution s’initialise au premier tour, pas au démarrage. Si vous interrogez le point d’accès juste après un redémarrage, vous le verrez : n’y lisez pas une panne.

À chaque tour, le backend écrit une ligne de journal cloak: {treffer, platzhalter, ungescannt, notizen} — des compteurs, jamais du texte en clair. La première preuve en direct issue de la production, le 19 août 2026 (NanoGPT/GLM via le protocole OpenAI), ressemblait à ceci : le modèle a répondu avec le jeton au lieu de la clé de test GitHub, journal treffer {secret/key.github: 2}, platzhalter 1, ungescannt 0.

Un exemple de la façon dont un prompt arrive chez le fournisseur :

Vous écrivez :
  Pourquoi l’appel échoue-t-il ? Dans le .env il y a
  ANTHROPIC_API_KEY=sk-ant-… et le serveur répond 401.
  Écrivez-moi à max.mustermann@example.org quand vous aurez fini.

Le fournisseur voit :
  Pourquoi l’appel échoue-t-il ? Dans le .env il y a
  ANTHROPIC_API_KEY=CLOAK_SECRET_… et le serveur répond 401.
  Écrivez-moi à CLOAK_EMAIL_… quand vous aurez fini.

Un chantier ouvert, nommé honnêtement : il existe une copie de contrôle par tour sous GET /api/privacy/transparenz/{sitzung}, avec les remplacements, l’identifiant de règle, l’emplacement et le prompt masqué (backend/api/transparency_routes.py, branchée depuis le 3 septembre 2026). La route est construite et testée — 32 plus 74 plus 11 tests dans le dépôt —, mais le producteur qui y écrit les données à chaque tour n’est, hors des tests, appelé nulle part pour l’instant. C’est consigné ainsi dans docs/OFFENE_PUNKTE.md. La route renvoie donc aujourd’hui des listes vides. Nous la mentionnons pour que vous ne la preniez pas pour une preuve.

Ce que cloak ne sait pas faire

Cette liste est la partie sans laquelle l’article ne serait que de la publicité. Elle provient des limites de la fiche technique et des fichiers sources eux-mêmes.

  • Basé sur des motifs. Un format sans préfixe, sans contexte d’affectation et d’entropie inférieure à 4,0 passe au travers. Un mot de passe comme Sommer2026! sans password= devant n’est pas reconnu. C’est pourquoi la description du produit ne promet volontairement pas que les clés échapperont au modèle dans tous les cas ; un test frontend (test_werbeaussagen.mjs) bloque la formulation absolue.
  • Ni images, ni PDF, ni base64. La capture d’écran d’un .env n’est pas analysée. Les blocs de réflexion (thinking) sont signés ; ils sont comptés, pas modifiés.
  • Les noms propres dans le texte courant seulement via le coffre (cloak add person). Un modèle de nom naïf avait produit 594 faux positifs rien que dans la documentation, lors du test. Numéros de téléphone, IBAN et numéros de carte produisent des constats, mais pas de remplacement. Noms d’hôte, IP et chemins ne sont pas dans le pipeline.
  • Le chemin CLI n’est couvert qu’à moitié — et c’est aujourd’hui le chemin de l’instance hébergée. Selon /api/health du 7 septembre 2026, deux accès claude-instanz et l’accès opérateur sont actifs sur ultranos.de ; anthropic-api, openai et ollama ne le sont pas. Sur le chemin CLI, le prompt et le prompt système sont masqués, y compris avec --resume. Mais la CLI exécute les outils elle-même et garde sa propre connexion vers Anthropic, qu’UltraNOS n’observe pas. Les résultats d’outils de la CLI — précisément la « source la plus productive » évoquée plus haut — ne passent pas par cloak. Un jeton dans l’appel d’outil reste non résolu et échoue sans conséquence. La couverture complète, avec résultats d’outils masqués à chaque tour et substitution inverse, vaut sur les chemins API que vous choisissez avec votre propre clé.
  • E-mail et personne apparaissent en clair dans le stockage des messages d’UltraNOS à cause de reveal=true ; person n’est pas redétectée de façon fiable au tour suivant.
  • La latence n’est pas mesurée. Seuls sont établis le budget de règle de 2000 ms par analyse et une optimisation du temps de verrouillage de 31,5 ms à 0,01 ms dans la copie de contrôle. Le journal des modifications ne contient aucun protocole de mesure pour le chemin de masquage lui-même.
  • Ni synchronisation, ni export, ni TTL. Si le fichier de clé du coffre est perdu, les entrées deviennent illisibles. L’extraction de souvenirs dispose de son propre filet, grossier, et « ne remplace pas cloak » (backend/memory/erinnerung.py).

Ce que nous pouvons prouver : 128 tests dans la suite cloak (cloak/tests/), 17 tests pour le chemin CLI, une campagne de mesure sur le dépôt lui-même sans fausse alerte, une preuve en direct issue de la production. Ce que nous n’affirmons pas : l’exhaustivité.

Pour qui ce n’est pas fait

Si vous n’utilisez que des modèles locaux sans connexion réseau, le prompt ne quitte pas votre machine ; cloak résout alors un problème que vous n’avez pas. Si votre entreprise a conclu avec le fournisseur du modèle un contrat Enterprise avec zéro rétention et son propre traitement sous-traité, le contrat est votre levier, et le masquage n’est tout au plus qu’une seconde laisse. Et si vous transmettez surtout des secrets sous forme de captures d’écran ou de PDF, cloak ne vous protège pas aujourd’hui. Pour tous ceux entre les deux — les développeuses et développeurs qui utilisent des agents de code avec accès aux fichiers et ne peuvent pas contrôler chaque prompt individuellement — le remplacement avant l’envoi est le point où se joue la différence. cloak est décrit sur la page d’accueil comme un composant à part entière ; vous trouverez d’autres articles sur les rouages dans le blog.

FAQ

Le modèle voit-il jamais la clé ?

Sur les chemins API (Anthropic, OpenAI, Ollama et fournisseurs personnalisés utilisant ces protocoles), non, pourvu qu’un motif corresponde : le jeton part, l’outil ne reçoit la vraie valeur qu’à l’exécution, et le résultat est de nouveau masqué avant le tour suivant. Sur le chemin CLI, cela ne vaut que pour le prompt et le prompt système ; ce que la CLI lit elle-même, le modèle le voit. Un format sans motif reconnaissable passe sur tous les chemins.

Qu’en est-il des mots de passe dans les sorties de terminal ?

Sur les chemins API, chaque sortie d’outil passe par le même pipeline que le prompt ; sont reconnus les mots de passe avec contexte, par exemple password=…, mysql -p…, redis-cli -a …, --password …, les chaînes de connexion et les en-têtes Authorization. Un mot de passe seul sur une ligne, sans ancre de ce type, n’est pas reconnu. Sur le chemin CLI, les sorties de terminal de la CLI ne sont pas analysées du tout.

Cela fonctionne-t-il avec ma propre clé API ?

Oui, et c’est la voie recommandée. Si vous enregistrez dans UltraNOS votre propre clé Anthropic ou compatible OpenAI, votre trafic passe par le protocole API avec la couverture complète : résultats d’outils à chaque tour, substitution inverse à l’appel. Les règles des fournisseurs sur la conservation des données se trouvent sous docs.anthropic.com et platform.openai.com/docs.

Cela répond-il aux exigences du RGPD ?

On ne s’octroie pas cette attestation soi-même, et nous ne le faisons pas. Ce qui est démontrable : serveur situé à Nuremberg, un contrat de sous-traitance au sens de l’art. 28 du RGPD est disponible, et clés, identifiants et adresses e-mail sont remplacés par des jetons avant l’envoi, pourvu qu’un motif corresponde. Ce que le fournisseur du modèle fait du reste du prompt relève de son contrat avec vous. Que les prompts partent vers le fournisseur choisi, cloak n’y change rien.

Puis-je l’héberger moi-même ?

L’auto-hébergement est optionnel et possible sur demande — le dépôt n’est pas public. Sur votre propre matériel, UltraNOS tourne avec un script de démarrage ; cloak est inclus comme paquet Python sans dépendance d’exécution et s’active via l’indicateur ULTRANOS_FEATURE_CLOAK. La valeur par défaut de l’indicateur est désactivée ; une fois activé, le principe fail-closed s’applique : si le paquet manque, le tour échoue au lieu d’envoyer du texte en clair. Le coffre se trouve alors sur votre machine, avec les limites mentionnées plus haut pour la synchronisation et l’export.

Vos clés restent chez vous : le reste tourne dans le navigateur.

UltraNOS est l’IDE web agentique : des agents IA en parallèle, un éditeur, git et un terminal. cloak remplace les clés d’API et les mots de passe avant qu’un prompt n’atteigne le fournisseur. Hébergé en Allemagne, 14 jours gratuits.

Essayer 14 jours gratuitement →

À lire ensuite