From f1067906d63779021ed63a01e0d5ef5958ed5610 Mon Sep 17 00:00:00 2001 From: Mkmuniz Date: Sat, 3 Oct 2026 02:24:11 -0300 Subject: [PATCH 1/3] docs: drop the claim that no maintained tool detects Brazilian data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "No maintained open-source tool detects CPF, CNPJ, CNH, Pix keys, card PANs and E2EIDs. Not underserved — empty." is not true: at least one maintained project detects and validates Brazilian identifiers in free text. A README that makes a checkable claim and gets it wrong costs more credibility than the claim was ever worth. The paragraph now says what vedei does rather than what others do not: it validates Brazilian data by check digit instead of matching by shape, and it enforces at the boundary where data leaves — agent context, CI, logs — in one Go binary with no runtime and no network. --- README.md | 2 +- README.pt-BR.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e78446e..3c3c90d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/README.pt-BR.md b/README.pt-BR.md index 76a2879..2435d61 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -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. From 9ad892ef01c9ace605cddcdc031d0066b659cc8f Mon Sep 17 00:00:00 2001 From: Mkmuniz Date: Sat, 3 Oct 2026 02:25:14 -0300 Subject: [PATCH 2/3] fix(docs): the alphanumeric CNPJ was first issued on 2026-07-31, not 07-06 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Receita Federal generated the first alphanumeric CNPJ on 2026-07-31; the 2026-07-06 date in seven places came from an early timeline, not from the authority. Corrected everywhere it appeared — README, the package doc in cnpj.go, test comments, the corpus header — with the gov.br announcement linked from both READMEs. The corpus gains that first CNPJ itself: 00.000.000/E08G-12, a Banco do Brasil branch, published by the Receita. Every other CNPJ vector in the suite is generated, which only proves the algorithm agrees with itself; this one proves it agrees with the issuer. It validates — the check digits computed from 00000000E08G are 1 and 2. Its fingerprint joins .vedeiignore, so the self-scan stays clean. Corpus: 41 cases, precision and recall 1.000. --- .vedeiignore | 4 +++- README.md | 2 +- README.pt-BR.md | 2 +- corpus/cases/cnpj.txt | 8 +++++++- detect/br/cnpj.go | 2 +- detect/br/cnpj_test.go | 2 +- detect/br/fuzz_test.go | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.vedeiignore b/.vedeiignore index e1bebc6..cdbccaa 100644 --- a/.vedeiignore +++ b/.vedeiignore @@ -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 @@ -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. diff --git a/README.md b/README.md index 3c3c90d..4038bcd 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/README.pt-BR.md b/README.pt-BR.md index 2435d61..21d0b22 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -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 | diff --git a/corpus/cases/cnpj.txt b/corpus/cases/cnpj.txt index 546e000..ffaf6d7 100644 --- a/corpus/cases/cnpj.txt +++ b/corpus/cases/cnpj.txt @@ -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 diff --git a/detect/br/cnpj.go b/detect/br/cnpj.go index b0018e1..9239add 100644 --- a/detect/br/cnpj.go +++ b/detect/br/cnpj.go @@ -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 diff --git a/detect/br/cnpj_test.go b/detect/br/cnpj_test.go index 0b7ca3f..32b4a48 100644 --- a/detect/br/cnpj_test.go +++ b/detect/br/cnpj_test.go @@ -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++ { diff --git a/detect/br/fuzz_test.go b/detect/br/fuzz_test.go index 02b636b..d5b7304 100644 --- a/detect/br/fuzz_test.go +++ b/detect/br/fuzz_test.go @@ -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) From f3481d0f6d26ab278527ea439b87497a3899ad46 Mon Sep 17 00:00:00 2001 From: Mkmuniz Date: Sat, 3 Oct 2026 02:36:40 -0300 Subject: [PATCH 3/3] docs: explain the name vedei, replacing the leftover nadzor etymology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "The name" section still explained Надзор, Russian for oversight, with "(vedei)" pasted into it by the rename — an etymology for a word the project no longer uses. Vedei is Portuguese: the first person past tense of vedar, to seal or keep from passing. "Eu vedei" — I sealed it. 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. Written in both READMEs, and no reference to the old name remains anywhere in the repository. --- README.md | 4 ++-- README.pt-BR.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4038bcd..85d2b25 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.pt-BR.md b/README.pt-BR.md index 21d0b22..6ad8c00 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -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