gitsafety
Não deixa você commitar uma chave de API.
Você instala uma vez, e a partir daí o git commit avisa antes de a credencial sair da sua
máquina. Funciona em repositório git, em pasta solta e em notebook Jupyter.
Status: pré-1.0. Publicado no PyPI e funcional — instale e use. O
1.0.0fica reservado para depois de uso sustentado em trabalho real. Testes e revisão provam corretude;1.0.0deveria significar uso, e são coisas diferentes.
Começando — 3 passos
Precisa de Python 3.10 ou mais novo. Nada de Docker, nada para compilar, nada para subir.
1. Instale
pipx install gitsafety
Se não tiver o pipx: python3 -m pip install --user pipx && python3 -m pipx ensurepath.
Prefere pip num ambiente virtual? Leia isto antes.
pip install gitsafety funciona, mas o hook chama gitsafety pelo PATH na hora do
commit. Se o ambiente virtual não estiver ativo naquele momento, todo commit falha com
gitsafety: not found. O pipx deixa o comando disponível sempre.
Descobrimos isso instalando no nosso próprio repositório — está registrado em
knowledge-base/dogfood/.
2. Ligue no seu projeto
Dentro da pasta do repositório, uma vez só:
gitsafety install
hook instalado em /home/ana/meu-projeto/.git/hooks/pre-commit
A partir de agora o commit é verificado.
Emergência: git commit --no-verify
3. Trabalhe normalmente
Não há passo 4. O git commit continua sendo git commit — a diferença aparece só quando
há uma credencial no que você está enviando:
$ git commit -m "adiciona cliente S3"
app.py:6 aws-access-key-id AKIA••••••••••••MPLE
1 segredo encontrado.
Revogue a chave no provedor antes de qualquer outra coisa.
O commit não aconteceu. Tire a chave do código (use variável de ambiente, cofre, o que o seu time usar), commite de novo, e pronto.
Por que "revogue a chave" e não "apague a linha". Se a chave já saiu da sua máquina alguma vez, apagá-la do código não a desativa. Quem a copiou continua com ela. Revogar no provedor é a única ação que resolve de verdade — o resto é limpeza.
Já commitei uma chave antes de instalar. E agora?
O hook protege daqui para frente. Para olhar para trás:
gitsafety scan --history
antigo.py:1 postgres-connection-string post•••••••••••••••••••••••••••••.com
6773ba6b Ana 2026-07-28T08:35:13-03:00
1 segredo encontrado no histórico.
Revogue a chave no provedor antes de qualquer outra coisa.
Remover o arquivo agora NÃO apaga o segredo do histórico.
Ele mostra quando a chave entrou e por quem — é o que decide a urgência. E funciona mesmo que o arquivo já tenha sido apagado: o histórico do git lembra, e qualquer pessoa que clonou o repositório também.
Os três comandos, e quando usar cada um
| Comando | O que olha | Quando você usa |
|---|---|---|
gitsafety install |
— | Uma vez por repositório, no começo |
| (o hook, sozinho) | o que você está commitando | Automático, a cada git commit |
gitsafety scan |
os arquivos da pasta agora | Antes de abrir um PR, ou por curiosidade |
gitsafety scan --history |
tudo que já foi commitado | Ao adotar num projeto que já existe |
Emergência: git commit --no-verify passa por cima do hook. Existe porque bloquear alguém
sem saída faz a pessoa desinstalar a ferramenta.
Este README é o guia de uso. A referência completa da CLI está em
docs/API.md.
Por que ele não enche o saco
O hook verifica apenas as linhas que você está introduzindo — não o repositório inteiro, e nem mesmo o arquivo inteiro. Para conteúdo de texto, medido: o commit fica ~0,04 s mais lento, independente de tocar 1 ou 200 arquivos (benchmark).
Commitar binário é outra história: o hook lê o conteúdo para não deixar um segredo
passar disfarçado de arquivo binário, e isso custa. Medido: 30 MB de binário no mesmo
commit levam ~4,5 s. Não é o caso comum — mas se o seu primeiro commit com a ferramenta
inclui uma pasta de assets, é bom saber. Ponha o caminho em ignore: se ele nunca vai
conter credencial.
Isso tem uma consequência que vale saber: se um arquivo já tinha um segredo commitado
antes e você edita outra linha dele, o hook não reclama. É deliberado — do contrário,
adotar a ferramenta num repositório com história bloquearia todo commit até alguém
limpar o passado. Para achar o que já está lá, use gitsafety scan na pasta inteira.
E ele só dispara em padrão conhecido de credencial — AKIA seguido de 16
maiúsculas é uma chave da AWS, não tem outra leitura. Nada de heurística de
"parece aleatório", que é o que enche relatório de falso positivo e faz o time
desligar a ferramenta na segunda semana.
A única regra que olha o contexto em vez do valor é a genérica: ela exige o nome da
variável (password, api_key, aws_secret_access_key…), o operador de atribuição, e um
valor de 20+ caracteres com dígito e letra. Isso é o que separa uma credencial de um
identificador de código — secret_key = settings.SECRET_KEY não casa, token = os.environ[...]
não casa. Medido: zero falsos positivos em 72.570 linhas de código dos projetos de
referência, e 3 num corpus maior de 1,3 milhão de linhas — a classe deles está descrita
logo abaixo.
Ela tem fronteira, e vale saber qual. Não pega: valor em outra linha, valor montado por
concatenação, senha com símbolo nos primeiros 20 caracteres ("S3nh4@Sup3r..."), senha só
de letras, e nomes que ela não conhece (pwd, credential). Pega às vezes demais:
anotação de tipo em Python tem a mesma forma de um segredo em YAML, e a regra não distingue
as duas . Foram 3 ocorrências em 1,3 milhão de linhas de código
real. Para essas, use allow: ou ignore:.
O que ele detecta
Sem configurar nada:
| Categoria | Regras | Exemplos |
|---|---|---|
| Cloud | 8 | AWS, Google Cloud, Azure, DigitalOcean, Heroku, Cloudflare |
| Git / pacotes | 11 | GitHub (ghp_, github_pat_, gho_, ghs_, ghr_), GitLab, npm, PyPI, RubyGems, crates.io |
| IA e dados | 6 | OpenAI (sk-), Anthropic (sk-ant-), Hugging Face, Cohere, Replicate, W&B |
| Pagamentos e SaaS | 19 | Stripe, Twilio, SendGrid, Slack, Sentry, Shopify, Atlassian, Linear, JWT |
| Chaves privadas | 4 | Blocos PEM, PuTTY, PKCS#8 cifrada, age |
| Banco de dados | 5 | Strings de conexão com senha: PostgreSQL, MySQL, MongoDB, Redis, AMQP |
| Genéricas | 1 | Credencial atribuída a variável de nome revelador: aws_secret_access_key, password, api_key, token, client_secret… |
54 padrões no total. Cada um traz seus próprios exemplos de acerto e de não-acerto, verificados a cada execução da suíte.
O que for específico do seu time entra no YAML — veja abaixo.
Notebooks Jupyter
.ipynb é tratado como caso de primeira classe: o gitsafety lê o JSON do notebook e
verifica o código das células e também as saídas salvas. É onde a chave escapa
com mais frequência — você apaga a célula, mas o print(os.environ) de três
execuções atrás continua gravado no arquivo que vai para o commit.
O achado aponta a célula, não a linha do JSON:
analise.ipynb :: célula 4 (saída):1 postgres-connection-string post•••••••••.com
Um notebook aberto no Jupyter não tem linha 50, então reportar a linha do arquivo não
ajudaria ninguém a achar o segredo. Saídas de print, de resultado de célula e de
traceback de erro são todas verificadas — o traceback de uma chamada autenticada que
falhou costuma guardar a credencial inteira.
Notebook corrompido ou truncado não quebra a varredura: ele volta a ser lido como texto, porque um arquivo que o parser recusa ainda pode conter a chave.
Histórico
O hook impede que a chave entre. Para saber se ela já entrou antes:
gitsafety scan --history
config.py:1 aws-access-key-id AKIA••••••••••••MPLE
b7cc2556 Ana 2026-07-27T15:33:54-03:00
1 segredo encontrado no histórico.
Revogue a chave no provedor antes de qualquer outra coisa.
Remover o arquivo agora NÃO apaga o segredo do histórico.
O commit mostrado é o da introdução — "desde quando esta chave está exposta?" é a pergunta que decide a urgência. Apagar o arquivo hoje não resolve: o objeto continua no histórico de todo mundo que já clonou o repositório.
Um segredo que aparece em vários commits vira um achado, com a contagem ao lado quando foi reintroduzido depois de sair.
O custo é proporcional às linhas do histórico, não aos commits. No próprio repositório do gitsafety — 74 commits, 77 mil linhas adicionadas — leva cerca de 2,5 segundos (benchmark). É um comando para rodar de vez em quando, não a cada commit; para isso existe o hook.
O que ele não vê — e avisa. Se você reescreveu o histórico com git reset, rebase ou
commit --amend, o commit antigo saiu das referências e o --history não o alcança. Ele
diz isso em vez de deixar você concluir que está limpo:
Nenhum segredo encontrado.
Atenção: 1 commit reescrito não foi verificado.
Se foi para remover uma chave, revogue-a: reescrever não desfaz a exposição.
O objeto continua no seu repositório local por cerca de 90 dias, recuperável pelo reflog. E reescrever o histórico nunca desfez uma exposição: revogar a chave no provedor é a única ação que resolve.
Configuração
Opcional. Sem arquivo nenhum, os padrões embutidos valem. Para ajustar, crie um
.gitsafety.yml na raiz do repositório:
# .gitsafety.yml — as três chaves são opcionais
# Caminhos que nem são abertos (glob)
ignore:
- "tests/fixtures/**"
- "docs/exemplos/**"
# Valores conhecidos e inofensivos (texto exato ou regex)
allow:
- "AKIAIOSFODNN7EXAMPLE" # chave de exemplo da documentação da AWS
- "sk-test-.*" # chaves do ambiente de teste do Stripe
# Seus próprios padrões
rules:
- id: chave-interna
pattern: "INTERNAL_KEY_[A-Za-z0-9]{20}"
- id: token-do-cliente
pattern: "cli_[a-f0-9]{32}"
Três chaves de topo — ignore, allow, rules — e nada mais. Sem herança de
config, sem condition: AND/OR, sem regra composta.
Chave com erro de digitação não é ignorada. ignroe: para o scan e sugere ignore: —
o silêncio custaria a você uma sessão de depuração descobrindo que a config nunca foi lida.
Seus padrões são verificados antes de rodar. Um regex inválido vira erro com o nome da
regra. Um regex que poderia travar a verificação no meio de um commit — como
(a{1,50}){1,50} — é recusado na carga, com a explicação. É o seu commit que estaria
pendurado.
YAML inválido ou regex que não compila param o scan com erro apontando a linha (exit code 2). Nunca são ignorados em silêncio.
Outro arquivo: gitsafety scan --config caminho/config.yml.
Quer pegar senha solta também?
Não vem ligado, porque gera falso positivo. Se o seu time aceita a troca, cole isto
no rules::
- id: senha-hardcoded
pattern: "(?i)(password|senha|secret|token|api_key)\\s*[=:]\\s*['\"][^'\"]{8,}['\"]"
Ignorando um finding
Da forma mais local para a mais ampla:
1. Na linha — para segredo de teste commitado conscientemente:
API_KEY = "sk-test-4eC39HqLyjWDarjtT1zdp7dc" # gitsafety: allow
2. Por valor — no allow:, quando o mesmo valor aparece em vários arquivos.
3. Por caminho — no ignore:, quando a pasta inteira é irrelevante.
Sobre o hook
gitsafety install escreve .git/hooks/pre-commit chamando
gitsafety scan --staged. Não depende do framework pre-commit nem de qualquer
outra ferramenta.
Se já existir um pre-commit no repositório, o comando recusa e avisa em vez de
sobrescrever o seu hook — ele te mostra a linha para adicionar no hook existente.
No CI
Qualquer runner com Python. Em GitHub Actions:
- name: Verifica segredos
run: |
pipx install gitsafety
gitsafety scan --history
Exit code 1 quando encontra segredo, o que já reprova o job.
Saída e exit codes
O segredo aparece mascarado por padrão — o relatório não pode virar o próximo
vazamento. --show-secrets mostra o valor completo quando você realmente precisa.
| Exit code | Significado |
|---|---|
0 |
Nada encontrado |
1 |
Segredo encontrado |
2 |
Erro (config inválida, caminho inexistente, não é repositório git) |
Todas as flags
gitsafety install instala o hook de pre-commit
gitsafety scan [CAMINHO] verifica arquivos
--staged apenas o que está no index do git
--history o histórico do git, em vez do disco
--show-secrets mostra o segredo completo
--config PATH arquivo de config (padrão: .gitsafety.yml)
gitsafety --version mostra a versão instalada
Quatro flags no scan, e é o teto. Se você sentir falta de uma quinta, o caso
provavelmente é do .gitsafety.yml — flag é interface que todo mundo carrega para sempre;
configuração é escolha de quem precisa dela.
--staged e --history são alvos e por isso mutuamente exclusivos: o primeiro olha o que
você está commitando, o segundo o que já foi commitado, e sem nenhum dos dois ele olha o
disco.
Esta lista é a lista inteira. gitsafety scan --help mostra exatamente estas flags, e um
teste da suíte compara as duas nas duas direções a cada execução — flag documentada que não
existe, e flag que existe sem documentação.
O contrato completo — cada código de saída, cada campo da saída, cada chave de configuração
e o que acontece quando ela está errada — está em docs/API.md.
O que o gitsafety não faz
Fora de escopo de propósito — cada item é complexidade que o público não pediu:
- Não remove o segredo do histórico. Detectar e reescrever histórico são
problemas diferentes; reescrita é destrutiva e fica com
git filter-repo/ BFG. - Não é cofre de senhas nem rotaciona credenciais.
- Não escaneia dentro de
.zip/.tar.gznem decodifica base64 e hex. - Não usa entropia nem herança de config, regra composta ou
condition AND/OR. - Não emite CSV, JUnit, SARIF nem template — saída humana e exit code.
- Não roda como serviço nem tem imagem Docker.
Precisa de algo dessa lista? gitleaks e trufflehog cobrem esse território — é a recomendação honesta.
Regra número um
Segredo detectado é segredo comprometido. Apagar a linha, refazer o commit ou
adicionar ao allow: não desfaz a exposição.
- Revogue e rotacione a chave no provedor.
- Só depois limpe o código.
O gitsafety encontra; quem fecha a porta é você.
Licença
Implementação própria, sob licença MIT (ver LICENSE).
A abordagem de hook de pre-commit com catálogo de padrões conhecidos é prática consagrada na área — gitleaks é a referência mais completa. Nenhum código foi copiado.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gitsafety-0.7.4.tar.gz.
File metadata
- Download URL: gitsafety-0.7.4.tar.gz
- Upload date:
- Size: 63.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7410ad6e31787cb213ce4bfb2fde1bef08553b293e845fab6538cb898340085b
|
|
| MD5 |
f7a1bdbf86030eb7e91cc5b8ad1580e0
|
|
| BLAKE2b-256 |
e13ed72880a45665e7be10560f3690440207ba038846f6b615cf11874448efc8
|
File details
Details for the file gitsafety-0.7.4-py3-none-any.whl.
File metadata
- Download URL: gitsafety-0.7.4-py3-none-any.whl
- Upload date:
- Size: 65.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a35c80dfa11d4288c5c085a3ca4cca10c9b6bf36dc73a73a9d235042a73e72f4
|
|
| MD5 |
965aa4f8161f797e2da0391b021bc006
|
|
| BLAKE2b-256 |
c207d896bd20e089b317164d67011514aa503360c87bdec2d3ca7c78c5c9754e
|