Hinweis: Zach Rice, Leiter des Secrets-Scan bei Aikido, ist auch der Gründer von Gitleaks. Dieser Beitrag erschien ursprünglich auf seinem Blog, wo er Themen wie Secrets-Scan, Software Engineering und Open Source behandelt.
In Regex is (almost) All You Need haben wir gelernt, dass die Kombination aus regulären Ausdrucksmustern, Entropie und regelbasierten Filtern eine effektive Methode ist, um potenzielle Secrets zu erkennen. Regex wird verwendet, um ein breites Netz auszuwerfen und Kandidaten zu identifizieren. Entropie dient als primärer Filter für die erfassten Kandidaten, und zusätzliche Filter wie das Vorhandensein häufig verwendeter englischer Wörter oder das Filtern nach bekannten „sicheren“ Dateien wie go.sum werden zuletzt angewendet.
Entropie leistet gute Arbeit beim Filtern von Fehlalarmen, lässt aber insbesondere bei der Bewertung generischer Secrets viel zu wünschen übrig. Könnte es etwas Besseres als Entropie für diesen primären Filter nach der Regex-Erfassung geben? Dieser Beitrag untersucht, ob Byte-Pair Encoding als effektivere Alternative zur Entropie für den Secrets-Scan dienen kann.

Entropie
Was ist eigentlich Entropie? Laut John von Neumann im Gespräch mit Claude Shannon, „niemand weiß es wirklich“, aber Wikipedia weiß es. Die Shannon-Entropie misst die durchschnittliche Unvorhersehbarkeit einer Zeichenkette, d.h. wie viele Informationen jedes Zeichen trägt. Wenn Zeichen gleichmäßig verteilt sind (viele verschiedene Zeichen, kein klares Muster), ist jedes einzelne schwerer vorherzusagen, daher ist die Entropie hoch. Wenn einige wenige Zeichen dominieren, ist das nächste Zeichen leicht zu erraten, daher ist die Entropie niedrig. In der Praxis bedeutet das, dass etwas wie aaaaaa111111 niedrig punktet, während etwas wie xA9fP2qL0sRw hoch punktet. Im Hinblick auf die Secrets detection macht dies die Entropie zu einem brauchbaren ersten Schritt, um „zufällig aussehende“ Zeichenketten (potenzielle Secrets) zu erkennen.
Aber wollen wir wirklich, dass Zufälligkeit unser primärer Filter für die Secrets detection ist? Man verzeihe mir die „Es ist nicht X, es ist Y“-LLM-Floskel – aber Secrets sind nicht nur zufällig, sie sind statistisch ungewöhnlich im Vergleich zur natürlichen Verteilung von menschlich geschriebenem Text. Einfacher ausgedrückt: Secrets sind selten. Eine b64-kodierte Zeichenkette, eine UUID, ein tatsächliches Secret und eine seltsam aussehende Abhängigkeitszeichenkette können ähnliche Entropie-Scores aufweisen, obwohl sie sich grundlegend darin unterscheiden, wie oft sie in der realen Welt vorkommen. Entropie kann nicht zwischen „das sieht zufällig aus“ und „das taucht fast nie in englischem Text oder Quellcode auf“ unterscheiden. Anstatt Zufälligkeit mit Entropie zu messen, was wäre, wenn wir versuchen würden zu messen, wie außerhalb des Vokabulars oder wie nicht-natürlichsprachlich eine Zeichenkette ist.
Byte-Pair Encoding
Wie erkennen wir also, wie nicht-natürlichsprachlich oder selten eine Zeichenkette ist? Byte-Pair Encoding (BPE) natürlich! Die Byte-Pair Encoding Tokenisierung spiegelt implizit die Häufigkeitsverteilung des Textes wider, auf dem sie trainiert wurde. Häufige Wörter und Subwörter werden zu langen Tokens zusammengeführt, während seltene oder unnatürliche Zeichenketten in viele kurze Tokens zerlegt werden.
Hier sind ein paar Beispiele mit dem cl100k_base Tokenizer1:
- „Hello World“ → [15339, 1917]
- „lookingatcomputer“ → [20986, 266, 44211]
- „kj2h3f2fuaafewa“ → [93797, 17, 71, 18, 69, 17, 69, 4381, 2642, 365, 64]
Da BPE sein Vokabular durch wiederholtes Zusammenführen der häufigsten Zeichenpaare in den Trainingsdaten aufbaut, spiegelt seine Tokenisierung natürlich wider, wie häufig verschiedene Muster auftreten. Das klingt doch ein bisschen nach dem Seltenheitsmerkmal, das wir messen wollen, oder?
Häufige englische Wörter erhalten eigene individuelle Tokens, da sie im Training häufig vorkommen, z. B. ist „password“ Token [3918]. „github“ ist Token [5316]. „function“ ist Token [1723]. Aber ein zufälliger API-Schlüssel wie `ghp_xK7mP9qL2wR5nT3vJ8fY`?

Der Tokenizer hat diese spezifische Sequenz im Training wahrscheinlich noch nie gesehen, daher zerlegt er den String in kleinere Paare und greift schließlich auf einzelne Bytes zurück, die dann zu den Tokens [876, 79, 3292, 42, 22, 76, 47, 24, 80, 43, 17, 86, 49, 20, 77, 51, 18, 85, 41, 23, 69, 56] tokenisiert werden. Das sind 22 Tokens für einen 24 Zeichen langen String, was bedeutet, dass der Tokenizer kaum etwas darin erkannt hat.
Schauen Sie sich https://tiktokenizer.vercel.app/?model=cl100k_base an, um zu sehen, wie verschiedene Strings tokenisiert werden.
Token-Effizienz
Wenn BPE-Tokenizer seltene Strings in viele kurze Tokens zerlegen, dann können wir die Seltenheit eines Strings messen, indem wir die Länge des ursprünglichen Strings mit der Anzahl der erzeugten Tokens vergleichen. Nennen wir es doch einfach Token-Effizienz.token_efficiency = len(string) / len(tokens)
Natürliche Sprache passt gut zum Vokabular des Tokenizers, daher erzeugen häufige Phrasen weniger Tokens. Secret-ähnliche Strings tun dies nicht, daher erzeugen sie viele Tokens.
Betrachten wir unser Beispiel von ghp_xK7mP9qL2wR5nT3vJ8fY. Es hat eine Token-Effizienz von 1,1 (ein 24 Zeichen langer String, der 22 Tokens erzeugt). Eine Phrase wie Hello World hat eine Effizienz von 3,7 (11 Zeichen, aufgeteilt in 3 Tokens). Wenn Secrets durchweg niedrigere Token-Effizienzwerte und alltäglicher Text höhere Werte erzeugen, dann könnte die Token-Effizienz ein nützlicher Post-Regex-Filter für die Secrets detection sein.
Um diese Idee zu testen, können wir uns dem CredData-Datensatz zuwenden, der Tausende von gelabelten Beispielen echter Secrets und Nicht-Secrets aus realen Repositories enthält. Wenn die Token-Effizienz tatsächlich Seltenheit oder „Nicht-Alltagssprachlichkeit“ abbildet, dann könnte die Betrachtung der Verteilung der Token-Effizienz auf den Secret-Werten des CredData-Datensatzes eine Lücke zwischen Secrets und Nicht-Secrets aufzeigen.
CredData
Der CredData-Datensatz ist in Indexdateien und Datendateien aufgeteilt. Die Indexdateien speichern die benötigten Metadaten, wie Labels, Zeilen- und Spaltenbereiche sowie Dateinamen. Sie enthalten nicht die eigentlichen Secret-Werte, daher müssen Sie jedes Secret durch Slicing der Quelldateien in den angegebenen Bereichen rekonstruieren. Das ist der Ansatz, den ich gewählt habe. Ich habe jeden gelabelten Secret-Wert direkt aus dem Datensatz extrahiert. Das bedeutet, wir bewerten nicht, ob die Token-Effizienz Secrets eigenständig erkennen kann. Stattdessen bewerten wir, ob die Token-Effizienz bereits erfasste Kandidaten-Secrets klassifizieren kann, was sie zu einem Post-Regex-Filterungsschritt und nicht zu einem eigenständigen Detektor macht.
Den Code, der diese Diagramme erzeugt, finden Sie hier:

Das sieht vielversprechend aus! Es sieht so aus, als wäre 2,5 ein guter Mindest-Cutoff für die Token-Effizienz. Gitleaks verwendet einen Entropie-Cutoff von 3,5 für generische Secrets.
Betrachten wir die Klassifizierungen unter Verwendung dieser Cutoffs.

Token-Effizienz: Genauigkeit=57,3% Recall=98,6% F1=0,725
Entropie: Präzision=21,1% Recall=70,4% F1=0,325Ein Recall von 98,6 % ist ziemlich gut. Wir klassifizieren fast alle echten Secrets korrekt und lassen dabei nur 149 False Negatives unberücksichtigt. Es gibt eine ordentliche Anzahl von False Positives für die Token-Effizienz, aber der Unterschied zwischen dieser und der Entropie ist wie Tag und Nacht. Die Entropie erzeugt 28k FPs (fast 4x mehr als die Token-Effizienz) und 3k FNs. Das Hinzufügen eines einfachen Wortfilters hilft beiden Methoden, aber die Token-Effizienz gewinnt immer noch beim F1-Score. Der Wortfilter ignoriert Secrets mit mehr als einem Vorkommen eines Wortes mit 4 oder mehr Zeichen.

TE + Wortfilter: Genauigkeit=80,4% Recall=95,8% F1=0,874
Entropie + Wortfilter: Präzision=76,6% Recall=67,1% F1=0,715Dieser Filter leistet speziell für die Entropie einen Großteil der Arbeit, hilft uns aber auch beim Filtern von FPs für die Token-Effizienz. Für die Token-Effizienz reduzierten wir die FPs von 7894 auf 2508, während wir durch die Anwendung dieses Wortfilters nur 308 neue FNs einführten, was uns erheblich bei der Verbesserung des F1-Scores hilft.
Wenn Sie versuchen möchten, diese Ergebnisse zu reproduzieren, können Sie einen Teil des Codes auf meinem Github einsehen.
Beispiele
Werfen wir einen Blick auf einige Secrets, die die Entropie übersieht, aber die Token-Effizienz erkennt.
e2aa9ae57d893a1
Dieser Wert hat eine Entropie von 3,125. Ziemlich hoch, aber nicht ganz 3,5, was Gitleaks und einige andere Secret-Detektoren als Grenzwert verwenden. e2aa9ae57d893a1 erzeugt [68, 17, 5418, 24, 6043, 3226, 67, 26088, 64, 717] für seine cl100k_base Tokens, was eine Token-Effizienz von 1,6 ergibt, weit unter dem Grenzwert von 2,5 für die Token-Effizienz.
mcjrx4
Hier haben wir ein Passwort, und kein besonders gutes dazu. Passwörter sind eine schwierige Kategorie für den Entropie-Filter, da sie oft (und leider) kurz sind und kurze Strings typischerweise niedrige Entropiewerte aufweisen. Dieses hat eine Entropie von nur 2,58. Aber der Tokenizer zerlegt es in nahezu Byte-Level-Tokens [13183, 73, 12940, 19], was ihm eine Token-Effizienz von 1,5 verleiht. Sechs Zeichen, vier Tokens. Der Tokenizer erkennt es nicht als natürliche Sprache, und genau das ist das Signal, das wir wollen.
U@kkf8fo!!
Ein weiteres Passwort. Dieses ist aufgrund der Sonderzeichen interessant. Eine der Herausforderungen bei der Secrets detection, speziell für generische Secrets und Passwörter, ist die Erstellung eines Regex, der erfasst die meisten Secrets. Das Problem bei der Verwendung eines Regex, der darauf abzielt, zu erfassen die meisten Secrets ist, dass er das Potenzial hat, viele False Positives durchzulassen, wie E-Mails, URLs usw. Für jedes Sonderzeichen wie @ oder ! oder / das Sie in der Zeichenklasse Ihrer Capture-Gruppe definieren, erhöhen Sie die Wahrscheinlichkeit, mehr False Positives zuzulassen. Aus diesem Grund ist die generische Capture-Gruppe von Gitleaks ziemlich streng: [\w.=-]{10,150}. Mit einem Token-Effizienz-Filter könnten wir dieses Muster potenziell lockern, um mehr Sonderzeichen einzuschließen. Okay, mit diesem Kontext vergleichen wir hier, wie Entropie und Token-Effizienz für dieses Beispiel abschneiden. U@kkf8fo!! hat eine Entropie von 2,72 und erzeugt diese Tokens [52, 31, 19747, 69, 23, 831, 51447] mit einer Token-Effizienz von 1,42 (10 Zeichen, 7 Tokens).
Ein kurzer Hinweis zu Passwörtern. Die Token-Effizienz ist nicht gut darin, schlechte Passwörter wie „password123“ oder „chibearsfan123“ zu klassifizieren. Diese Passwörter sind im Grunde natürliche Sprache, was einen hohen Token-Effizienz-Wert bedeutet. Passphrasen funktionieren ebenfalls nicht gut, da es sich dabei meist um einfache Wörter handelt.
Performance
Die Auswirkungen auf die Performance sind vernachlässigbar. Die durchschnittliche Zeit pro String zur Berechnung der Entropie bei den erfassten CredData Secrets beträgt 4,55 µs gegenüber 11,75 µs zur Berechnung der Token-Effizienz2 (unter Verwendung von cl100k_base). Ein 2,5-facher Unterschied mag viel erscheinen, aber man muss bedenken, dass bei der Secrets detection der Engpass die regulären Ausdrücke sind, nicht die schnellen Filter wie Entropie oder Token-Effizienz, die danach kommen.
Token-Effizienz mit Betterleaks
Die Betreuer des CredData-Datensatzes haben einen beeindruckenden Secret-Scanner namens CredSweeper entwickelt, der Regex, Entropie und RNNs zur Erkennung von Secrets verwendet. In einer Welt voller Aussagen wie „LLMs können Secrets mit NULL False Positives erkennen“ (sowohl in der Wissenschaft als auch in der Industrie3), ist es erfrischend zu sehen, wie die Ingenieure bei Samsung einen Secret-Detektor auf der Grundlage von „traditionellerem Machine Learning“ entwickeln. Anerkennung. CredSweeper weist einen beeindruckenden F1-Score von 0,85 auf, wenn er gegen CredData getestet wird. Das ist ziemlich gut! Mal sehen, ob wir ihn mit dem neuen Token-Effizienz-Filter in Betterleaks schlagen können.
Ach ja. Was ist Betterleaks? Es ist ein neues Projekt, das auf dem Erbe von Gitleaks aufbaut. Ich werde in einem anderen Beitrag mehr darüber erzählen, aber alles, was Sie wissen müssen, ist, dass es ein Drop-in-Ersatz für Gitleaks ist, den ich pflege... und es wird besser sein... wegen des Namens.
Diese Konfiguration fügt ein paar neue Regeln hinzu und optimiert einige Kleinigkeiten in der bestehenden Standardkonfiguration. Die Verwendung dieser Konfiguration und das Ausführen von Betterleaks gegen den CredData-Datensatz ergibt einen F1-Score von 0,892.
(Token-Effizienz + (geringe) Entropie bei generischen Regeln + Regelanpassungen) Benchmark-Ergebnisse:
========================================
TP (True Positives): 10796
FP (Falsch-Positive): 1031
TN (Echte Negative): 42572
FN (Falsch-Negative): 1578
----------------------------------------
Genauigkeit: 0,9534
Präzision: 0,9128
Recall: 0,8725
F1-Wert: 0,8922Ziemlich gut.

Allein die Verwendung von Token Efficiency liefert uns:
(Nur Token-Effizienz + Regelanpassungen) Benchmark-Ergebnisse:
========================================
TP (True Positives): 10843
FP (Falsch-Positive): 1722
TN (Echte Negative): 41881
FN (Falsch-Negative): 1531
----------------------------------------
Genauigkeit: 0,9419
Präzision: 0,8630
Recall: 0,8763
F1-Wert: 0,8696Ohne einen niedrigen Entropie-Grenzwert bei der generischen Regel in Kombination mit dem Token Efficiency Filter führen wir ca. 700 FPs ein. Dennoch erreichen wir ohne diesen Entropie-Filter bei der generischen Regel einen F1-Wert von .86, was nicht schlecht ist.
Wie schneiden wir ab, wenn wir den Token Efficiency Filter nicht verwenden, sondern uns stattdessen nur auf Regelanpassungen und Entropie verlassen?
(Nur Entropie + Regelanpassungen) Benchmark-Ergebnisse:
========================================
TP (True Positives): 8498
FP (Falsch-Positive): 1041
TN (Echte Negative): 42562
FN (Falsch-Negative): 3876
----------------------------------------
Genauigkeit: 0,9122
Präzision: 0,8909
Recall: 0,6868
F1-Wert: 0,7756Also ist .892 vs. .776 ein ziemlich großer Unterschied. Die alleinige Verwendung des Entropie-Filters fügt über 2000 FNs und 80 FPs im Vergleich zum Token Efficiency Filter hinzu.
Den Code für den Token Efficiency Filter finden Sie auf dem Betterleaks Github.
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
}Der Filter ist leicht angepasst im Vergleich zu dem, der im Diagrammvergleich verwendet wurde. Dies berücksichtigt kurze Passwörter und Secrets mit Zeilenumbrüchen (wir entfernen Zeilenumbrüche, bevor wir die Token Efficiency Analyse am Kandidaten durchführen).
Weitere Hinweise:
- Ich konnte das von CredData bereitgestellte Benchmarking-Skript nicht zum Laufen bringen, daher habe ich (Claude) mein eigenes erstellt. Entschuldigen Sie, falls dies keine wissenschaftlich einwandfreie Methode ist, aber Sie können meine (Claudes) Arbeit überprüfen, da sie Open Source ist.
- Duplikate wurden aus dem CredData-Datensatz entfernt.
- Alle neuen Regeln und Anpassungen bestehender Regeln waren nicht „Secret-spezifisch“. Das heißt, ich habe versucht, den Benchmark nicht zu manipulieren.
- Einige Secrets, die im CredData-Datensatz als FPs gekennzeichnet sind, scheinen fälschlicherweise gekennzeichnet zu sein, daher könnte der F1-Wert ehrlich gesagt um +/- 0,05 abweichen.
1 Für alle Beispiele, die wir betrachten, werden wir den cl100k_base Tokenizer verwenden.
2 Die Zeitmessung stammt aus dem Diagrammgenerator-Skript, das Entropie und Token Efficiency für jedes Kandidaten-Secret berechnet.
Danksagungen: Ich möchte dem GitHub-Benutzer „DmitriyAlergant“ dafür danken, dass er diese Idee in einem Issue im Gitleaks-Repo eingereicht hat. Ein großes Dankeschön geht auch an die Maintainer von CredData/CredSweeper, die mich in dieses Thema eingeführt haben.

