Nota: Zach Rice, Head de varredura de segredos na Aikido, também é o fundador da Gitleaks. Esta publicação apareceu originalmente em seu blog, onde ele aborda varredura de segredos, engenharia de software e tópicos de código aberto.
Em Regex is (almost) All You Need, aprendemos que usar uma combinação de padrões de expressão regular, entropia e filtros baseados em regras é uma forma eficaz de detectar Secrets candidatos. Regex é usado para lançar uma rede ampla e identificar candidatos. A entropia é usada como um filtro primário nos candidatos capturados e filtros adicionais, como a presença de palavras em inglês comumente usadas ou a filtragem em arquivos “seguos” conhecidos como go.sum, são aplicados por último.
A entropia faz um trabalho razoável na filtragem de falsos positivos, mas deixa muito a desejar, especialmente ao avaliar Secrets genéricos. Poderia haver algo melhor que a entropia para esse filtro primário pós-captura de regex? Este post examina se o Byte-Pair Encoding pode servir como uma alternativa mais eficaz à entropia para varredura de Secrets.

Entropia
O que é entropia, afinal? Segundo John von Neumann, ao conversar com Claude Shannon, “ninguém realmente sabe”, mas a Wikipédia sabe. A Entropia de Shannon mede a imprevisibilidade média de uma string, ou seja, quanta informação cada caractere carrega. Quando os caracteres são distribuídos uniformemente (muitos caracteres distintos, sem padrão claro), cada um é mais difícil de prever, então a entropia é alta. Quando poucos caracteres dominam, o próximo caractere é fácil de adivinhar, então a entropia é baixa. Na prática, isso significa algo como aaaaaa111111 pontua baixo, enquanto algo como xA9fP2qL0sRw pontua alto. Em relação à detecção de Secrets, isso torna a entropia uma primeira passagem decente para identificar strings "com aparência aleatória" (Secrets candidatos).
Mas será que realmente queremos que a aleatoriedade seja nosso filtro primário para a detecção de Secrets? Com licença pelo clichê de LLM "não é X, é Y" aqui - mas Secrets não são apenas aleatórios, eles são estatisticamente incomuns em comparação com a distribuição natural de texto escrito por humanos. Em termos mais simples, Secrets são raros. Uma string codificada em b64, um UUID, um Secret real e uma string de dependência com aparência estranha podem ter pontuações de entropia semelhantes, apesar de serem fundamentalmente diferentes na frequência com que aparecem no mundo real. A entropia não consegue diferenciar entre "isso parece aleatório" e "isso quase nunca aparece em texto em inglês ou código-fonte". Em vez de medir a aleatoriedade com entropia, e se tentássemos medir o quão fora do vocabulário ou o quão não-linguagem-natural uma string é.
Byte-pair encoding
Então, como detectamos o quão não-linguagem-natural ou rara uma string é? Byte-Pair Encoding (BPE), é claro! A tokenização por Byte-Pair Encoding reflete implicitamente a distribuição de frequência do texto no qual foi treinada. Palavras e subpalavras comuns são mescladas em tokens longos, enquanto strings raras ou não naturais são quebradas em muitos tokens curtos.
Aqui estão alguns exemplos usando o tokenizer cl100k_base1:
- "Hello World" → [15339, 1917]
- "lookingatcomputer" → [20986, 266, 44211]
- "kj2h3f2fuaafewa" → [93797, 17, 71, 18, 69, 17, 69, 4381, 2642, 365, 64]
Como o BPE constrói seu vocabulário mesclando repetidamente os pares de caracteres mais comuns nos dados de treinamento, sua tokenização reflete naturalmente a frequência com que diferentes padrões aparecem. Parece um pouco com aquela raridade que estamos tentando medir, não é?
Palavras comuns em inglês recebem seus próprios tokens individuais porque aparecem frequentemente no treinamento, por exemplo, "password" é o token [3918]. "github" é o token [5316]. "function" é o token [1723]. Mas uma chave de API aleatória como `ghp_xK7mP9qL2wR5nT3vJ8fY`?

O tokenizer provavelmente nunca viu essa sequência específica durante o treinamento, então ele quebra a string em pares menores, eventualmente recorrendo a bytes individuais que acabam sendo tokenizados para [876, 79, 3292, 42, 22, 76, 47, 24, 80, 43, 17, 86, 49, 20, 77, 51, 18, 85, 41, 23, 69, 56]. Isso significa 22 tokens para uma string de 24 caracteres, o que significa que o tokenizer mal reconheceu algo nela.
Confira https://tiktokenizer.vercel.app/?model=cl100k_base para ver como diferentes strings são tokenizadas.
Eficiência de token
Se os tokenizers BPE quebram strings raras em muitos tokens curtos, então podemos medir o quão rara uma string é comparando o comprimento da string original com o número de tokens produzidos. Ora, vamos chamar de Token Efficiency.token_efficiency = len(string) / len(tokens)
A linguagem natural se alinha bem com o vocabulário do tokenizer, então frases comuns produzem menos tokens. Strings semelhantes a Secrets não se alinham, então elas produzem muitos tokens.
Considere nosso exemplo de ghp_xK7mP9qL2wR5nT3vJ8fY. Ele tem uma eficiência de token de 1.1 (uma string de 24 caracteres produzindo 22 tokens). Uma frase como Hello World tem uma eficiência de 3.7 (11 caracteres divididos em 3 tokens). Se Secrets consistentemente produzem pontuações de eficiência de token mais baixas e textos do dia a dia produzem pontuações mais altas, então a eficiência de token poderia ser um filtro pós-regex útil para a detecção de Secrets.
Para testar essa ideia, podemos recorrer ao dataset CredData, que contém milhares de exemplos rotulados de Secrets verdadeiros e não-Secrets extraídos de repositórios do mundo real. Se a eficiência de token realmente rastreia a raridade ou a "não-linguagem-do-dia-a-dia", então observar a distribuição da eficiência de token nos valores de Secrets dos datasets CredData pode revelar uma lacuna entre Secrets e não-Secrets.
CredData
O dataset CredData é dividido em arquivos de índice e arquivos de dados. Os arquivos de índice armazenam os metadados necessários, como rótulos, intervalos de linha e coluna, e nomes de arquivo. Eles não contêm os valores reais dos Secrets, então você precisa reconstruir cada Secret fatiando os arquivos de origem nos intervalos especificados. Essa foi a abordagem que adotei. Extraí cada valor de Secret rotulado diretamente do dataset. Isso significa que não estamos avaliando se a eficiência de token pode detectar Secrets por si só. Em vez disso, estamos avaliando se a eficiência de token pode classificar Secrets candidatos já capturados, o que a torna uma etapa de filtragem pós-regex em vez de um detector autônomo.
Você pode dar uma olhada no código que produz esses gráficos aqui:

Isso parece promissor! Parece que 2.5 é um bom limite mínimo para a Eficiência de Token. Gitleaks usa um limite de entropia de 3.5 para Secrets genéricos.
Usando esses limites, vamos dar uma olhada nas classificações.

Eficiência do token: Precisão=57,3% Recuperação=98,6% F1=0,725
Entropia: Precisão=21,1% Recuperação=70,4% F1=0,325Um recall de 98.6% é muito bom. Estamos classificando corretamente quase todos os Secrets verdadeiros, deixando apenas 149 falsos negativos. Há uma quantidade razoável de falsos positivos para a Eficiência de Token, mas a diferença entre isso e a entropia é gritante. A entropia gera 28k FPs (quase 4x mais que a Eficiência de Token) e 3k FNs. Adicionar um filtro de palavras simples ajuda ambos os métodos, mas a eficiência de token ainda vence no score F1. O filtro de palavras ignora Secrets com mais de uma ocorrência de uma palavra de 4 ou mais caracteres.

TE + Filtro de palavras: Precisão=80,4% Recuperação=95,8% F1=0,874
Filtro de Entropia + Palavras: Precisão=76,6% Recuperação=67,1% F1=0,715Este filtro faz grande parte do trabalho pesado especificamente para a entropia, mas também nos ajuda a filtrar FPs para a eficiência de token. Para a eficiência de token, passamos de 7894 FPs → 2508 FPs, introduzindo apenas 308 novos FNs ao aplicar este filtro de palavras, o que nos ajuda significativamente com o score F1.
Se você quiser tentar reproduzir esses resultados, pode conferir parte do código no meu Github.
Exemplos
Vamos dar uma olhada em alguns Secrets que a entropia não detecta, mas a eficiência de token sim.
e2aa9ae57d893a1
Este tem uma entropia de 3.125. Bem alta, mas não chega a 3.5, que é o que Gitleaks e alguns outros detectores de Secrets usam como limite. e2aa9ae57d893a1 produz [68, 17, 5418, 24, 6043, 3226, 67, 26088, 64, 717] para seus tokens cl100k_base, o que resulta em uma eficiência de token de 1.6, bem abaixo do limite de eficiência de token de 2.5.
mcjrx4
Aqui temos uma senha, e não uma muito boa. Senhas são uma categoria difícil para o filtro de entropia porque são frequentemente (e infelizmente) curtas, e strings curtas geralmente têm baixos valores de entropia. Esta tem uma entropia de apenas 2.58. Mas o tokenizador a divide em tokens de quase nível de byte [13183, 73, 12940, 19], dando-lhe uma eficiência de token de 1.5. Seis caracteres, quatro tokens. O tokenizador não a reconhece como linguagem natural, e esse é exatamente o sinal que queremos.
U@kkf8fo!!
Outra senha. Esta é interessante por causa dos caracteres especiais. Um dos desafios na detecção de segredos, especificamente para Secrets genéricos e senhas, é criar uma regex que capture a maioria dos Secrets. O problema de usar uma regex que visa capturar a maioria dos Secrets é que ela tem o potencial de permitir muitos falsos positivos, como e-mails, URLs, etc. Então, para cada caractere especial como @ ou ! ou / você define na classe de caracteres do seu grupo de captura, você aumenta as chances de permitir mais falsos positivos. Por causa disso, podemos ver que o grupo de captura genérico do Gitleaks é bastante rigoroso: [\w.=-]{10,150}. Com um filtro de eficiência de token, poderíamos potencialmente flexibilizar esse padrão para incluir mais caracteres especiais. Ok, então com esse contexto, veja como a entropia e a eficiência de token se comparam para este exemplo. U@kkf8fo!! tem uma entropia de 2.72 e produz estes tokens [52, 31, 19747, 69, 23, 831, 51447] com uma eficiência de token de 1.42 (10 caracteres, 7 tokens).
Uma breve nota sobre senhas. A Eficiência de Token não se sai bem na classificação de senhas ruins como “password123” ou “chibearsfan123”. Essas senhas são basicamente linguagem natural, o que significa um alto valor de eficiência de token. Frases-senha também não se saem bem porque geralmente são apenas palavras diretas.
Desempenho
O impacto no desempenho é insignificante. O tempo médio por string para calcular a entropia nos Secrets CredData capturados é de 4.55 µs contra 11.75 µs para calcular a eficiência de token2 (usando cl100k_base). Uma diferença de 2.5x pode parecer muito, mas é preciso lembrar que, quando se trata de detecção de segredos, o gargalo são as expressões regulares, e não os filtros rápidos como entropia ou eficiência de token que vêm depois.
Eficiência de token com Betterleaks
Os mantenedores do conjunto de dados CredData criaram um impressionante scanner de Secrets chamado CredSweeper que usa regex, entropia e RNNs para detectar Secrets. Em um mundo repleto de “LLMs podem detectar Secrets com ZERO falsos positivos” (tanto na academia quanto na indústria3), é revigorante ver os engenheiros da Samsung desenvolvendo um detector de Secrets baseado em “Machine Learning tradicional”. Mérito. O CredSweeper apresenta um impressionante score F1 de .85 quando testado contra o CredData. Isso é muito bom! Vamos ver se conseguimos superá-lo com o novo filtro de Eficiência de Token no Betterleaks.
Ah, sim. O que é Betterleaks? É um novo projeto que se baseia no legado do Gitleaks. Falarei mais sobre isso em outra publicação, mas tudo o que você precisa saber é que é um substituto direto para o Gitleaks que estou mantendo... e será melhor... por causa do nome.
Esta configuração adiciona algumas novas regras e ajusta pequenos detalhes na configuração padrão existente. Usar esta configuração e executar o Betterleaks contra o conjunto de dados CredData resulta em um score F1 de .892.
(Eficiência do token + (Baixa) entropia na regra genérica + Ajustes na regra) Resultados do benchmark:
========================================
TP (Verdadeiros Positivos): 10796
FP (Falsos Positivos): 1031
TN (Negativos Verdadeiros): 42 572
FN (Falsos Negativos): 1578
----------------------------------------
Precisão: 0,9534
Precisão: 0,9128
Recuperação: 0,8725
Pontuação F1: 0,8922Muito bom.

Usando apenas a Eficiência de Token, obtemos:
(Apenas eficiência de tokens + ajustes nas regras) Resultados dos testes de desempenho:
========================================
TP (Verdadeiros Positivos): 10843
FP (Falsos Positivos): 1722
TN (Negativos Verdadeiros): 41881
FN (Falsos Negativos): 1531
----------------------------------------
Precisão: 0,9419
Precisão: 0,8630
Recuperação: 0,8763
Pontuação F1: 0,8696Sem usar um corte de baixa entropia na regra genérica ao usar o filtro de Eficiência de Token, introduzimos aproximadamente 700 FPs. Ainda assim, sem esse filtro de entropia na regra genérica, obtemos um F1 de .86, o que não é ruim.
Como pontuamos sem o filtro de Eficiência de Token, mas dependendo apenas de ajustes de regra e entropia?
(Apenas ajustes na entropia e nas regras) Resultados dos testes de desempenho:
========================================
TP (Verdadeiros Positivos): 8498
FP (Falsos Positivos): 1041
TN (Negativos Verdadeiros): 42 562
FN (Falsos Negativos): 3876
----------------------------------------
Precisão: 0,9122
Precisão: 0,8909
Recuperação: 0,6868
Pontuação F1: 0,7756Certo, então .892 vs .776 é uma diferença bastante significativa. Usar apenas o filtro de entropia adiciona mais de 2000 FNs e 80 FPs em comparação com o filtro de Eficiência de Token.
Você pode ver o código para o filtro de Eficiência de Token no GitHub do 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
}O filtro é ligeiramente adaptado em comparação com o usado na comparação do gráfico. Isso é para levar em consideração senhas curtas e Secrets com quebras de linha (removemos as quebras de linha antes de executar a análise de Eficiência de Token no candidato).
Algumas outras observações:
- Não consegui fazer o script de benchmarking fornecido pelo CredData funcionar, então eu (claude) criei o meu próprio. Desculpe se isso é má ciência, mas ei, você pode verificar meu trabalho (claude), já que é de código aberto.
- Duplicatas foram removidas do conjunto de dados CredData.
- Todas as novas regras e ajustes às regras existentes não eram “específicos para Secrets”. Ou seja, tentei não manipular o benchmark.
- Alguns Secrets rotulados como FPs no conjunto de dados CredData parecem rotulados erroneamente, então, honestamente, o F1 poderia ser +/- .05 talvez.
1 Para todos os exemplos que analisamos, usaremos o tokenizer cl100k_base.
2 Tempo obtido do script gerador de gráfico que calcula a entropia e a eficiência de token para cada Secret candidato.
Agradecimentos: Gostaria de agradecer ao usuário do GitHub “DmitriyAlergant” por submeter esta ideia em uma issue no repositório do Gitleaks. Um grande agradecimento aos mantenedores do CredData/CredSweeper por me fazerem mergulhar neste tópico.

