Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .vedeiignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ aede264bea2e590f2e60467ab2ae74b8 # *************31 - 2x, detect/br/others_test.
cf4011fdac9970fb933927c3c2dc2e79 # *********80 - 1x, detect/br/others_test.go

# --- cnpj ---
# CNPJ test vectors, numeric and alphanumeric. The alphanumeric form takes effect 2026-07-06 and needs a fixture before then, not after.
# CNPJ test vectors, numeric and alphanumeric. The alphanumeric form was first issued on 2026-07-31.
e901dbbb99a1ba293aee61bf533bb180 # **.***.***/****-35 - 7x, detect/br/cnpj_test.go and 5 more
3e210b2f70e511a859c255d442cc346a # **.***.***/****-81 - 12x, detect/br/cnpj_test.go and 6 more
e186e56ca5d2b8fda5aa3be015e79407 # **.***.***/****-91 - 1x, detect/br/cnpj_test.go
Expand Down Expand Up @@ -88,6 +88,8 @@ f60e47e7fe914c3e5d10a9b2a99d8b96 # generated titulo vector
ec2a7642f9821664294f09e54573ca66 # generated CNS vector
83f7388d806b21f4c0c4ea6e6a651937 # Pix email key fixture

3d916d891e1aab79bc68592c2d7cd3a5 # 00.000.000/E08G-12, the first alphanumeric CNPJ the Receita issued (2026-07-31), a public record used as a corpus case

# Nothing is ignored by path, on purpose. Silencing *_test.go wholesale would
# also silence a credential genuinely committed into a test, which is a place
# credentials genuinely end up. Every entry above is one value, named.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Wire the hook into your agent: [`hooks/`](hooks/). Two binaries, because the spl
| **CPF** | ✅ | Mod 11, two check digits; repeated-sequence rejection |
| **CIN** | ✅ | Uses the CPF as the national number — same validation |
| **CNPJ** numeric | ✅ | Mod 11, weights `5,4,3,2,9,8,7,6,5,4,3,2` |
| **CNPJ** alphanumeric | ✅ | Mod 11 with `ASCII − 48` for letters — **in force since 2026-07-06** |
| **CNPJ** alphanumeric | ✅ | Mod 11 with `ASCII − 48` for letters — **first issued on 2026-07-31** ([Receita Federal](https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2026/julho/receita-federal-gera-o-primeiro-cnpj-em-formato-alfanumerico)) |
| **CNH** | ✅ | Mod 11, 11-digit variant |
| **PIS / NIS / NIT** | ✅ | Mod 11, weights `3,2,9,8,7,6,5,4,3,2` |
| **Título de eleitor** | ✅ | Two check digits, mod 11, embedded state code |
Expand Down Expand Up @@ -88,7 +88,7 @@ ISPB registry from [`guibranco/BancosBrasileiros`](https://github.com/guibranco/

## Why it exists

Secret scanning is solved and crowded — betterleaks ships 463 rules, kingfisher 1,051. Rebuilding that corpus loses. So vedei **imports betterleaks** (MIT) and spends everything on what nobody built: **Brazilian sensitive data**. No maintained open-source tool detects CPF, CNPJ, CNH, Pix keys, card PANs and E2EIDs. Not underserved — empty.
Secret scanning is solved and crowded — betterleaks ships 463 rules, kingfisher 1,051. Rebuilding that corpus loses. So vedei **imports betterleaks** (MIT) and spends its effort where global tools are weakest: **Brazilian sensitive data**, validated by check digit rather than matched by shape — CPF, CNPJ including the alphanumeric format, CNH, Pix keys and E2EIDs, card PANs with Brazilian brands. And it enforces at the boundary where data leaves: the agent's context, the CI run, the log line. One Go binary, no runtime, no network.

The second reason is architectural. betterleaks validates a secret by asking the provider over HTTP, which needs a rate limiter, a timeout budget, and care about where those requests go. Brazilian personal data doesn't work that way: a CPF is a mod-11 check digit, a card is Luhn, a Pix key is its type. **All offline.** No network, no rate limits, no SSRF surface. Simpler and safer, not just different.

Expand Down Expand Up @@ -163,9 +163,9 @@ corpus/ # labeled precision/recall harness (make corpus)

## The name

**Надзор** (*vedei*) is Russian for *oversight* — the word used for regulatory supervision. It is `dozor` (the watch) with the prefix `nad-` (over, above).
**Vedei** is Portuguese: the first person past tense of *vedar* — to seal, to block, to keep from passing. *Eu vedei*: "I sealed it."

A tool that inspects what passes a boundary and enforces a rule about it is not a guard. It is oversight.
It is what the person running the tool says once it has done its job. The CPF did not reach the model, the key did not reach the log, the card number did not reach the commit. Sealed.

## Contributing · Security · License

Expand Down
8 changes: 4 additions & 4 deletions README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Depois ligue o hook no seu agente: [`hooks/`](hooks/). São dois binários, e é
| **CPF** | ✅ | Módulo 11, dois DVs; rejeita sequências repetidas |
| **CIN** | ✅ | Usa o CPF como número nacional — mesma validação |
| **CNPJ** numérico | ✅ | Módulo 11, pesos `5,4,3,2,9,8,7,6,5,4,3,2` |
| **CNPJ** alfanumérico | ✅ | Módulo 11 com `ASCII − 48` nas letras — **vigente desde 06/07/2026** |
| **CNPJ** alfanumérico | ✅ | Módulo 11 com `ASCII − 48` nas letras — **primeiro emitido em 31/07/2026** ([Receita Federal](https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2026/julho/receita-federal-gera-o-primeiro-cnpj-em-formato-alfanumerico)) |
| **CNH** | ✅ | Módulo 11, variante de 11 dígitos |
| **PIS / NIS / NIT** | ✅ | Módulo 11, pesos `3,2,9,8,7,6,5,4,3,2` |
| **Título de eleitor** | ✅ | Dois DVs, módulo 11, com código de UF |
Expand Down Expand Up @@ -87,7 +87,7 @@ Base de ISPB do [`guibranco/BancosBrasileiros`](https://github.com/guibranco/Ban

## Por que existe

Detecção de segredo é problema resolvido e concorrido — betterleaks tem 463 regras, kingfisher 1.051. Reconstruir esse corpus é perder. Então o vedei **importa o betterleaks** (MIT) e gasta tudo no que ninguém construiu: **dado sensível brasileiro**. Nenhuma ferramenta open source mantida detecta CPF, CNPJ, CNH, chave Pix, PAN e E2EID. Não é mal servido — é vazio.
Detecção de segredo é problema resolvido e concorrido — betterleaks tem 463 regras, kingfisher 1.051. Reconstruir esse corpus é perder. Então o vedei **importa o betterleaks** (MIT) e concentra o esforço onde as ferramentas globais são mais fracas: **dado sensível brasileiro**, validado por dígito verificador em vez de casado por formato — CPF, CNPJ inclusive o alfanumérico, CNH, chave Pix e E2EID, PAN de cartão com bandeiras brasileiras. E aplica o controle na fronteira por onde o dado sai: o contexto do agente, a execução de CI, a linha de log. Um binário Go, sem runtime, sem rede.

A segunda razão é arquitetural. O betterleaks valida segredo perguntando ao provedor por HTTP, o que exige rate limiter, orçamento de timeout e cuidado com o destino das requisições. Dado pessoal brasileiro não funciona assim: CPF é módulo 11, cartão é Luhn, chave Pix é o tipo dela. **Tudo offline.** Sem rede, sem limite de vazão, sem superfície de SSRF. Mais simples e mais seguro, não só diferente.

Expand Down Expand Up @@ -162,9 +162,9 @@ corpus/ # harness rotulado de precision/recall (make corpus)

## O nome

**Надзор** (*vedei*) é "supervisão" ou "fiscalização" em russo — a palavra usada para supervisão regulatória. É `dozor` (a ronda) com o prefixo `nad-` (sobre, acima).
**Vedei** é o verbo *vedar* na primeira pessoa do pretérito perfeito — vedar como quem impede a passagem, como quem tampa uma fresta.

Uma ferramenta que inspeciona o que passa por uma fronteira e aplica uma regra sobre isso não é um guarda. É fiscalização.
É o que diz quem usa a ferramenta depois que ela fez o trabalho dela. O CPF não chegou ao modelo, a chave não chegou ao log, o número do cartão não chegou ao commit. Vedei.

## Contribuir · Segurança · Licença

Expand Down
8 changes: 7 additions & 1 deletion corpus/cases/cnpj.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# CNPJ — numeric and the alphanumeric format in force from 2026-07-06.
# CNPJ — numeric and the alphanumeric format first issued on 2026-07-31.

expect cnpj 11.222.333/0001-81
expect cnpj 11222333000181

# The first alphanumeric CNPJ the Receita Federal ever issued, on 2026-07-31: a
# Banco do Brasil branch. Not a generated vector — the real one, published by
# the Receita, so it proves the algorithm against the authority, not against
# itself.
expect cnpj 00.000.000/E08G-12

expect cnpj 00.000.000/0001-91 # the Uniao's CNPJ: body all zeros, but check digits close

reject 11.222.333/0001-82 # check digit wrong
Expand Down
2 changes: 1 addition & 1 deletion detect/br/cnpj.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var cnpjWeights = [12]int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
// ValidateCNPJ reports whether s is a structurally valid CNPJ, in either the
// numeric or the alphanumeric format.
//
// Since 2026-07-06 the Receita Federal issues alphanumeric CNPJs: the first
// Since 2026-07-31 the Receita Federal issues alphanumeric CNPJs: the first
// twelve positions may hold 0-9 and A-Z, while the last two remain numeric
// check digits. Letters enter the mod-11 sum as their ASCII value minus 48,
// so 'A' (65) contributes 17. Existing numeric CNPJs are unchanged, and a
Expand Down
2 changes: 1 addition & 1 deletion detect/br/cnpj_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestCNPJ_GeneratedNumericAreValid(t *testing.T) {
}
}

// The alphanumeric format has been in force since 2026-07-06.
// The alphanumeric format has been issued since 2026-07-31.
func TestCNPJ_GeneratedAlphanumericAreValid(t *testing.T) {
r := rand.New(rand.NewSource(11))
for i := 0; i < genCases; i++ {
Expand Down
2 changes: 1 addition & 1 deletion detect/br/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func FuzzCPFCheckDigits(f *testing.F) {
}

// FuzzCNPJCheckDigits does the same for both CNPJ formats. The alphanumeric one
// takes effect on 2026-07-06 and its weighting reads a letter as its ASCII code
// was first issued on 2026-07-31 and its weighting reads a letter as its ASCII code
// minus 48, which is the part most likely to be got wrong.
func FuzzCNPJCheckDigits(f *testing.F) {
f.Add("112223330001", false)
Expand Down
Loading