Note : Zach Rice, Responsable de l'analyse des secrets chez Aikido, est également le fondateur de Gitleaks. Cet article a été initialement publié sur son blog, où il aborde l'analyse des secrets, l'ingénierie logicielle et les sujets open source.
Dans Regex is (almost) All You Need, nous avons appris qu'utiliser une combinaison de motifs d'expressions régulières, d'entropie et de filtres basés sur des règles est un moyen efficace de détecter les secrets candidats. Les expressions régulières sont utilisées pour ratisser large afin d'identifier les candidats. L'entropie est utilisée comme filtre principal sur les candidats capturés, et des filtres supplémentaires, tels que la présence de mots anglais couramment utilisés ou le filtrage sur des fichiers « sûrs » connus comme go.sum, sont appliqués en dernier.
L'entropie fait un travail décent pour filtrer les faux positifs, mais elle laisse beaucoup à désirer, surtout lors de l'évaluation des secrets génériques. Pourrait-il y avoir quelque chose de mieux que l'entropie pour ce filtre primaire post-capture par regex ? Cet article examine si l'encodage par paires d'octets (Byte-Pair Encoding) peut servir d'alternative plus efficace à l'entropie pour l'analyse des secrets.

Entropie
Mais qu'est-ce que l'entropie ? Selon John von Neumann, lors d'une discussion avec Claude Shannon, “personne ne sait vraiment”, mais Wikipédia, si. L'entropie de Shannon mesure l'imprévisibilité moyenne d'une chaîne, c'est-à-dire la quantité d'informations que chaque caractère véhicule. Lorsque les caractères sont uniformément distribués (nombreux caractères distincts, pas de motif clair), chacun est plus difficile à prédire, l'entropie est donc élevée. Lorsqu'un petit nombre de caractères dominent, le caractère suivant est facile à deviner, l'entropie est donc faible. En pratique, cela signifie que quelque chose comme aaaaaa111111 obtient un score faible, tandis que quelque chose comme xA9fP2qL0sRw obtient un score élevé. En ce qui concerne la détection de secrets, cela fait de l'entropie une première étape décente pour repérer les chaînes « d'apparence aléatoire » (secrets candidats).
Mais voulons-nous vraiment que l'aléatoire soit notre filtre principal pour la détection de secrets ? Excusez le cliché des LLM « ce n'est pas X, c'est Y » ici – mais les secrets ne sont pas seulement aléatoires, ils sont statistiquement inhabituels par rapport à la distribution naturelle du texte écrit par l'homme. Plus simplement, les secrets sont rares. Une chaîne encodée en b64, un UUID, un secret réel et une chaîne de dépendance d'apparence étrange peuvent avoir des scores d'entropie similaires bien qu'ils soient fondamentalement différents quant à leur fréquence d'apparition dans le monde réel. L'entropie ne peut pas faire la différence entre « cela semble aléatoire » et « cela n'apparaît presque jamais dans le texte anglais ou le code source ». Au lieu de mesurer l'aléatoire avec l'entropie, et si nous essayions de mesurer à quel point une chaîne est hors vocabulaire ou non-langage naturel ?
Encodage par paires d'octets
Alors, comment détectons-nous à quel point une chaîne est non-langage naturel ou rare ? L'encodage par paires d'octets (BPE), bien sûr ! La tokenisation par encodage par paires d'octets reflète implicitement la distribution de fréquence du texte sur lequel elle a été entraînée. Les mots et sous-mots courants sont fusionnés en de longs tokens, tandis que les chaînes rares ou non naturelles sont décomposées en de nombreux tokens courts.
Voici quelques exemples utilisant le tokenizer cl100k_base1 :
- “Hello World” → [15339, 1917]
- “lookingatcomputer” → [20986, 266, 44211]
- “kj2h3f2fuaafewa” → [93797, 17, 71, 18, 69, 17, 69, 4381, 2642, 365, 64]
Parce que le BPE construit son vocabulaire en fusionnant à plusieurs reprises les paires de caractères les plus courantes dans les données d'entraînement, sa tokenisation reflète naturellement la fréquence d'apparition des différents motifs. Cela ressemble un peu à cette notion de rareté que nous essayons de mesurer, n'est-ce pas ?
Les mots anglais courants obtiennent leurs propres tokens individuels car ils apparaissent fréquemment dans l'entraînement, par exemple, « password » est le token [3918]. « github » est le token [5316]. « function » est le token [1723]. Mais une clé API aléatoire comme `ghp_xK7mP9qL2wR5nT3vJ8fY` ?

Le tokenizer n'a probablement jamais rencontré cette séquence spécifique lors de l'entraînement, il divise donc la chaîne en paires plus petites, finissant par revenir à des octets individuels qui sont finalement tokenisés en [876, 79, 3292, 42, 22, 76, 47, 24, 80, 43, 17, 86, 49, 20, 77, 51, 18, 85, 41, 23, 69, 56]. Cela représente 22 tokens pour une chaîne de 24 caractères, ce qui signifie que le tokenizer n'y a pratiquement rien reconnu.
Consultez https://tiktokenizer.vercel.app/?model=cl100k_base pour voir comment différentes chaînes sont tokenisées.
Efficacité des jetons
Si les tokenizers BPE décomposent les chaînes rares en de nombreux tokens courts, nous pouvons alors mesurer la rareté d'une chaîne en comparant la longueur de la chaîne originale au nombre de tokens produits. Appelons cela Efficacité des tokens.token_efficiency = len(string) / len(tokens)
Le langage naturel correspond bien au vocabulaire du tokenizer, de sorte que les phrases courantes produisent moins de tokens. Les chaînes de type secret, en revanche, ne le font pas, et produisent donc de nombreux tokens.
Considérons notre exemple de ghp_xK7mP9qL2wR5nT3vJ8fY. Il a une efficacité de token de 1.1 (une chaîne de 24 caractères produisant 22 tokens). Une phrase comme Hello World a une efficacité de 3.7 (11 caractères divisés en 3 tokens). Si les secrets produisent constamment des scores d'efficacité de token plus faibles et que le texte courant en produit de plus élevés, alors l'efficacité des tokens pourrait être un filtre post-regex utile pour la détection de secrets.
Pour tester cette idée, nous pouvons nous tourner vers le dataset CredData, qui contient des milliers d'exemples étiquetés de vrais secrets et de non-secrets extraits de dépôts réels. Si l'efficacité des tokens suit réellement la rareté ou le caractère "non-langage-courant", alors l'examen de la distribution de l'efficacité des tokens sur les valeurs secrètes des datasets CredData pourrait révéler un écart entre les secrets et les non-secrets.
CredData
Le dataset CredData est divisé en fichiers d'index et fichiers de données. Les fichiers d'index stockent les métadonnées nécessaires, telles que les étiquettes, les plages de lignes et de colonnes, et les noms de fichiers. Ils ne contiennent pas les valeurs secrètes réelles, vous devez donc reconstruire chaque secret en découpant les fichiers source aux plages spécifiées. C'est l'approche que j'ai adoptée. J'ai extrait chaque valeur secrète étiquetée directement du dataset. Cela signifie que nous n'évaluons pas si l'efficacité des tokens peut détecter des secrets par elle-même. Au lieu de cela, nous évaluons si l'efficacité des tokens peut classifier des secrets candidats déjà capturés, ce qui en fait une étape de filtrage post-regex plutôt qu'un détecteur autonome.
Vous pouvez consulter le code qui produit ces graphiques ici :

Cela semble prometteur ! Il semble que 2.5 soit un bon seuil minimal pour l'efficacité des tokens. Gitleaks utilise un seuil d'entropie de 3.5 pour les secrets génériques.
En utilisant ces seuils, examinons les classifications.

Efficacité des jetons : Précision =57,3% Rappel =98,6% F1 =0,725
Entropie: Précision=21,1% Rappel =70,4% F1 =0,325Un rappel de 98.6% est plutôt excellent. Nous classifions correctement presque tous les vrais secrets tout en ne laissant que 149 faux négatifs. Il y a un nombre décent de faux positifs pour l'efficacité des tokens, mais la différence entre cela et l'entropie est le jour et la nuit. L'entropie génère 28k FP (près de 4 fois plus que l'efficacité des tokens) et 3k FN. L'ajout d'un simple filtre de mots aide les deux méthodes, mais l'efficacité des tokens l'emporte toujours sur le score F1. Le filtre de mots ignore les secrets avec plus d'une occurrence d'un mot de 4 caractères ou plus.

TE + Filtre de mots : Précision =80,4% Rappel =95,8% F1 =0,874
Filtre entropie + mots : Précision =76,6% Rappel =67,1% F1 =0,715Ce filtre facilite grandement le travail pour l'entropie spécifiquement, mais nous aide également à filtrer les FP pour l'efficacité des tokens. Pour l'efficacité des tokens, nous sommes passés de 7894 FP à 2508 FP tout en n'introduisant que 308 nouveaux FN lors de l'application de ce filtre de mots, ce qui nous aide considérablement avec ce score F1.
Si vous souhaitez tenter de reproduire ces résultats, vous pouvez consulter une partie du code sur mon Github.
Exemples
Examinons quelques secrets qui échappent à l'entropie mais que l'efficacité des tokens détecte.
e2aa9ae57d893a1
Celui-ci a une entropie de 3,125. C'est assez élevé, mais pas tout à fait 3,5, ce que Gitleaks et d'autres détecteurs de secrets utilisent comme seuil. e2aa9ae57d893a1 produit [68, 17, 5418, 24, 6043, 3226, 67, 26088, 64, 717] pour ses tokens cl100k_base, ce qui donne une efficacité des tokens de 1,6, bien en dessous du seuil d'efficacité des tokens de 2,5.
mcjrx4
Nous avons ici un mot de passe, et pas un très bon qui plus est. Les mots de passe sont une catégorie difficile pour le filtre d'entropie car ils sont souvent (et malheureusement) courts, et les chaînes courtes ont généralement de faibles valeurs d'entropie. Celui-ci a une entropie de seulement 2,58. Mais le tokenizer le décompose en tokens quasi-niveau octet [13183, 73, 12940, 19], lui conférant une efficacité des tokens de 1,5. Six caractères, quatre tokens. Le tokenizer ne le reconnaît pas comme du langage naturel, et c'est exactement le signal que nous recherchons.
U@kkf8fo!!
Un autre mot de passe. Celui-ci est intéressant en raison de ses caractères spéciaux. L'un des défis dans la détection de secrets, spécifiquement pour les secrets génériques et les mots de passe, est de créer une expression régulière qui capture la plupart des secrets. Le problème avec l'utilisation d'une regex qui vise à capturer la plupart des secrets est qu'elle a le potentiel de laisser passer de nombreux faux positifs, comme des e-mails, des URL, etc. Ainsi, pour chaque caractère spécial comme @ ou ! ou / que vous définissez dans la classe de caractères de votre groupe de capture, vous augmentez les chances de laisser passer davantage de faux positifs. Pour cette raison, nous pouvons constater que le groupe de capture générique de Gitleaks est assez strict : [\w.=-]{10,150}. Avec un filtre d'efficacité des tokens, nous pourrions potentiellement assouplir ce motif pour inclure davantage de caractères spéciaux. Avec ce contexte, voici comment l'entropie et l'efficacité des tokens se comparent pour cet exemple. U@kkf8fo!! Il a une entropie de 2,72 et produit ces tokens [52, 31, 19747, 69, 23, 831, 51447] avec une efficacité des tokens de 1,42 (10 caractères, 7 tokens).
Une brève remarque sur les mots de passe. L'efficacité des tokens ne parvient pas à bien classer les mauvais mots de passe comme « password123 » ou « chibearsfan123 ». Ces mots de passe sont essentiellement du langage naturel, ce qui signifie une valeur d'efficacité des tokens élevée. Les phrases de passe ne fonctionnent pas bien non plus, car ce sont généralement de simples mots.
Performance
L'impact sur les performances est négligeable. Le temps moyen par chaîne pour calculer l'entropie sur les secrets CredData capturés est de 4,55 µs contre 11,75 µs pour calculer l'efficacité des tokens2 (en utilisant cl100k_base). Une différence de 2,5x peut sembler importante, mais il faut se rappeler qu'en matière de détection de secrets, le goulot d'étranglement réside dans les expressions régulières, et non dans les filtres rapides comme l'entropie ou l'efficacité des tokens qui viennent après.
Efficacité des jetons avec Betterleaks
Les mainteneurs du jeu de données CredData ont créé un scanner de secrets impressionnant appelé CredSweeper qui utilise des expressions régulières, l'entropie et des RNN pour détecter les secrets. Dans un monde rempli de « les LLM peuvent détecter les secrets avec ZÉRO faux positifs » (aussi bien dans le milieu universitaire que dans l'industrie3), il est rafraîchissant de voir les ingénieurs de Samsung développer un détecteur de secrets basé sur un « apprentissage automatique plus traditionnel ». Bravo. CredSweeper affiche un score F1 impressionnant de 0,85 lorsqu'il est testé sur CredData. C'est plutôt bon ! Voyons si nous pouvons le surpasser avec le nouveau filtre d'efficacité des tokens dans Betterleaks.
Ah oui. Qu'est-ce que Betterleaks ? C'est un nouveau projet qui s'appuie sur l'héritage de Gitleaks. J'en parlerai plus en détail dans un autre article, mais tout ce que vous devez savoir, c'est qu'il s'agit d'un remplacement direct de Gitleaks que je maintiens... et il sera meilleur... à cause de son nom.
Cette configuration ajoute quelques nouvelles règles et ajuste quelques petits éléments dans la configuration par défaut existante. L'utilisation de cette configuration et l'exécution de Betterleaks sur le jeu de données CredData donnent un score F1 de 0,892.
(Efficacité des jetons + (faible) entropie sur une règle générique + ajustements de la règle) Résultats des tests de performance :
========================================
TP (Vrais positifs) : 10796
FP (Faux positifs) : 1031
TN (Vrais négatifs) : 42 572
FN (Faux négatifs) : 1578
----------------------------------------
Précision : 0,9534
Précision: 0,9128
Rappel: 0,8725
Score F1 : 0,8922Plutôt bon.

En utilisant uniquement l'efficacité des tokens, nous obtenons :
(Efficacité des jetons + ajustements des règles) Résultats des tests de performance :
========================================
TP (vrais positifs) : 10843
FP (Faux positifs) : 1722
TN (Vrais négatifs) : 41 881
FN (Faux négatifs) : 1531
----------------------------------------
Précision : 0,9419
Précision: 0,8630
Rappel: 0,8763
Score F1 : 0,8696Sans utiliser de seuil d'entropie faible sur la règle générique lors de l'utilisation du filtre d'efficacité des tokens, nous introduisons environ 700 faux positifs. Néanmoins, sans ce filtre d'entropie sur la règle générique, nous obtenons un score F1 de 0,86, ce qui n'est pas mal.
Quel score obtenons-nous sans le filtre d'efficacité des tokens, mais en nous basant uniquement sur les ajustements de règles et l'entropie ?
(Juste Entropy + ajustements des règles) Résultats des tests de performance :
========================================
TP (vrais positifs) : 8498
FP (Faux positifs) : 1041
TN (Vrais négatifs) : 42 562
FN (Faux négatifs) : 3876
----------------------------------------
Précision : 0,9122
Précision: 0,8909
Rappel: 0,6868
Score F1 : 0,7756Ainsi, 0.892 contre 0.776 représente une différence assez significative. L'utilisation du seul filtre d'entropie ajoute plus de 2000 FN et 80 FP par rapport au filtre d'efficacité des tokens.
Le code du filtre d'efficacité des tokens est disponible sur le GitHub de Betterleaks.
func (d *Detector) failsTokenEfficiencyFilter(secret string) bool {
analyzed := secret
if len(analyzed) < 20 && strings.ContainsAny(analyzed, "\n\r") {
analyzed = newlineReplacer.Replace(analyzed)
}
tokens := d.tokenizer.Encode(analyzed, nil, nil)
matches := words.HasMatchInList(analyzed, 5)
if len(matches) > 0 {
return true
}
threshold := 2.5
if len(analyzed) < 12 {
threshold = 2.1
matches := words.HasMatchInList(analyzed, 3)
if len(matches) == 0 {
threshold = 2.5
}
}
return float64(len(analyzed))/float64(len(tokens)) >= threshold
}Le filtre est légèrement adapté par rapport à celui utilisé dans la comparaison graphique. Ceci afin de prendre en compte les mots de passe courts et les secrets contenant des retours à la ligne (nous supprimons les retours à la ligne avant d'exécuter l'analyse d'efficacité des tokens sur le candidat).
Quelques autres remarques :
- Je n'ai pas réussi à faire fonctionner le script de benchmarking fourni par CredData, j'ai donc (claude) créé le mien. Désolé si ce n'est pas très scientifique, mais vous pouvez vérifier mon travail (claude) puisqu'il est open source.
- Les doublons ont été supprimés du jeu de données CredData.
- Toutes les nouvelles règles et les ajustements aux règles existantes n'étaient pas « spécifiques aux secrets ». Autrement dit, j'ai essayé de ne pas biaiser le benchmark.
- Certains secrets étiquetés comme FP dans le jeu de données CredData semblent être étiquetés par erreur, donc honnêtement, le score F1 pourrait être de +\/ .05 environ.
1 Pour tous les exemples que nous examinerons, nous utiliserons le tokenizer cl100k_base.
2 Le temps est mesuré à partir du script de génération de graphique qui calcule l'entropie et l'efficacité des tokens pour chaque secret candidat.
Remerciements : Je tiens à remercier l'utilisateur GitHub « DmitriyAlergant » pour avoir soumis cette idée dans une issue sur le dépôt Gitleaks. Un grand merci aux mainteneurs de CredData\/CredSweeper pour m'avoir lancé dans cette quête.

