OSS · CLI em Bash · Git Hooks
private-lint
O histórico do git não esquece. Bloqueie nomes privados antes que entrem nele.
Um gate de git hooks que pega nomes pessoais e privados (nomes de família, e-mails pessoais, domínios de clientes e empregadores) antes do commit e do push. Bash puro, sem dependências. Os padrões de detecção vivem só na sua máquina, nunca em repositório algum, e um subcomando de auditoria do histórico completo cobre o momento antes de tornar um repositório público.
Sete caminhos por onde um nome vaza
"Escanear o diff" não bastou. Implantar a ferramenta em 25 repositórios públicos revelou ocorrências reais em caminhos que uma varredura de diff nunca enxerga. Um gate por caminho:
| Caminho | Gate | O que ele olha |
|---|---|---|
| Diff em stage + nomes de arquivo | pre-commit | Linhas adicionadas e nomes de caminho em stage (uma captura de tela salva com o nome de alguém vaza só pelo nome do arquivo) |
| Mensagem de commit | commit-msg | Cada linha do corpo da mensagem: "corrigido conforme sugestão do Yamada" é parado antes de virar histórico |
| Diff do intervalo do push | pre-push | Diffs e nomes de arquivo dos commits que o remoto ainda não viu; branches novos checam tudo que nenhum remoto alcançou |
| O destino do push | pre-push | Nome e URL do remote: um nome de cliente no slug do repo passa por qualquer quantidade de varredura de conteúdo |
| Identidade do autor | pre-commit | O e-mail efetivo de author/committer resolvido pelo git, comparado com uma allowlist; não configurado também conta como violação |
| O que já está no histórico | audit (manual) | Diffs do histórico completo, todos os nomes de arquivo, nome do repo, URLs de remote e o HEAD atual, varridos antes de abrir o repo |
| As próprias válvulas de escape | design | Regras de allow, marcadores inline e baselines de auditoria valem, cada um, um nível mais estreito que o campo ao lado |
Instalação
Clone e aponte para um repo. O install é idempotente e se insere antes dos hooks existentes, então um exit antecipado em um hook antigo não consegue contornar o gate. O install-template cobre cada clone futuro via init.templateDir.
git clone https://github.com/kenimo49/private-lint.git
cd private-lint
mkdir -p ~/.config/private-lint
$EDITOR ~/.config/private-lint/patterns # 1 ERE per line; never committed anywhere
./private-lint install ~/repos/<repo> # idempotent; inserts itself before existing hooks
echo "mode=block" > ~/repos/<repo>/.privatelintrc # public repos: block
./private-lint install-template # auto-install into every future clone / init Uso
Cada hook também roda na mão, e o audit é a varredura de pré-publicação:
./private-lint check --staged # pre-commit, by hand
./private-lint check --message <file> # commit-msg, by hand
./private-lint check --push-dest <name> <url> # remote name / URL
./private-lint audit ~/repos/<repo> # full history + repo name + remote + HEAD
./private-lint status # effective mode, hooks, pattern count
PRIVATE_LINT=off git commit ... # intentional bypass Quatro decisões de design
As decisões interessantes são todas sobre para que lado cada mecanismo falha.
-
Os gates falham abertos, a auditoria falha fechada
Numa máquina sem arquivo de padrões, os hooks deixam tudo passar: uma allowlist que bloqueia por padrão congelaria cada máquina onde caísse antes de alguém distribuir os padrões. O audit inverte isso e sai com erro quando os padrões faltam, porque "eu auditei" com zero padrões carregados é a saída mais perigosa que a ferramenta pode produzir.
-
identities é uma allowlist, não uma denylist
Os padrões dizem o que não pode aparecer. O identities diz quais e-mails de autor podem commitar. A inversão existe porque um repo sem user.email configurado herda em silêncio um endereço global ou autodetectado, e é exatamente assim que um endereço pessoal acaba num histórico público.
-
Três dos sete caminhos pegaram vazamentos reais durante a implantação
A checagem de destino do push, a de identidade e a auditoria do histórico completo marcaram, cada uma, uma ocorrência real enquanto a ferramenta era implantada nos repositórios existentes. Não são defesas hipotéticas.
-
As válvulas de escape têm escopo um nível mais estreito
O marcador inline (private-lint:allow) é ignorado em linhas de metadados gerados, como nomes de arquivo e URLs de remote, então não dá para contrabandeá-lo para campos que ele não deveria cobrir. As baselines de auditoria silenciam apenas ocorrências do histórico: um nome no nome atual do repo ou na URL do remote continua soando, porque isso é uma exposição ativa, não uma antiga já aceita.
Ferramentas relacionadas para devs
- hook-chain-lens A read-only CLI that reads every Claude Code hook scope (user, project, local, plugin) and prints what will actually fire in this cwd, in what order, from where.
- quake-lens Earthquake statistics CLI + MCP server in pure-stdlib Python: Gutenberg-Richter b-value (Aki MLE) and Omori-Utsu aftershock decay (Ogata MLE) from public catalogs.
- rhythm-lens Measures the rhythm of Japanese, English and Portuguese Markdown (sentence-length burstiness, paragraph structure) against measured human/AI distributions from two published papers. A writing feedback instrument, not an AI detector.
- claude-shift Multi-account Claude Code manager. Switch the active login, sync the two-file auth Claude Code actually reads (~/.claude/.credentials.json + ~/.claude.json), and watch 5-hour and weekly usage across all accounts. CLI + local API + Chrome extension + browser Web UI + Tauri v2 desktop.