
Encontre credenciais em capturas de tela, salve-as no seu gerenciador de segredos e remova-as irreversivelmente da imagem — local, offline, baseado em OCR.
Encontre credenciais em capturas de tela, extraia-as para um gerenciador de segredos e redija-as irreversivelmente das imagens — para que segredos não apodreçam em suas notas, capturas de tela e ~/Desktop.
Local. Offline. Silencioso. Sem rede, sem telemetria por padrão.
screenscrub faz a ponte entre duas categorias de ferramentas existentes: redatores GUI (Xnapper, Shhshot, macshot) desfocam PII manualmente, mas não conseguem distinguir um segredo de um endereço nem capturar o que ocultam; scanners de segredos (gitleaks, TruffleHog) encontram credenciais reais, mas apenas em texto, nunca em um PNG. screenscrub traz detecção de nível gitleaks — além de um pacote de red team e análise de entropia — para pixels, de forma headless: ele captura cada segredo para seu gerenciador, redige irreversivelmente a imagem e re-OCR o resultado para provar que o segredo desapareceu.
Uma rede de segurança, não uma garantia — screenscrub nunca afirma que uma imagem está "garantidamente limpa"; tanto OCR quanto detecção erram coisas, então sempre confira os resultados. Leia isto antes de confiar nele.
screenscrub scan ~/Screenshots # dry run: detect + report, no writes
screenscrub clean ~/Screenshots --out ~/clean \ # extract, redact, verify, quarantine
--sink file://./secrets.age --yes
screenscrub verify ~/clean # prove a folder is clean (exit≠0 if not)
screenscrub clean --keep-originals ./tmp
Cleaning 7 image(s) in ./tmp (7 worker(s))…
[1/7] tmp/ssh.jpg — 6 region(s) → tmp/ssh.redacted.jpg ✓ verified
[2/7] tmp/key.jpg — 13 region(s) → tmp/key.redacted.jpg ✓ verified
[3/7] tmp/test2.jpg — 12 region(s) → tmp/test2.redacted.jpg ✓ verified
[4/7] tmp/test5.jpg — 13 region(s) → tmp/test5.redacted.jpg ✓ verified
[5/7] tmp/test3.jpg — error: verification failed: 4 secret region(s) still detectable in tmp/test3.redacted.jpg; original preserved
[6/7] tmp/test4.jpg — error: verification failed: 9 secret region(s) still detectable in tmp/test4.redacted.jpg; original preserved
[7/7] tmp/test6.jpg — error: verification failed: 7 secret region(s) still detectable in tmp/test6.redacted.jpg; original preserved
7/7 file(s) flagged, 382 region(s) total, 3 file(s) errored/unverified.
By kind:
high-entropy 343
keyword-secret 15
db-connection-uri 10
github-pat 6
unix-crypt-hash 6
aws-access-key 1
stripe-key 1
Needs manual attention (3):
tmp/test3.jpg — error: verification failed: 4 secret region(s) still detectable in tmp/test3.redacted.jpg; original preserved
tmp/test4.jpg — error: verification failed: 9 secret region(s) still detectable in tmp/test4.redacted.jpg; original preserved
tmp/test6.jpg — error: verification failed: 7 secret region(s) still detectable in tmp/test6.redacted.jpg; original preserved
| Antes — valores de segredo visíveis | Após clean — irreversivelmente redigido |
![]() |
A mesma captura de tela antes e depois de clean: cada valor de segredo é decodificado em pixels, encapsulado e recodificado em uma nova imagem (irreversível) — rótulos e estrutura permanecem legíveis.
Requer o binário tesseract OCR no PATH:
brew install tesseractwinget install UB-Mannheim.TesseractOCR (ou o instalador UB Mannheim)apt-get install tesseract-ocrEntão compile:
go build -o screenscrub ./cmd/screenscrub
Compila de forma cruzada para macOS (arm64/amd64) e Windows (amd64) — ele utiliza o tesseract externamente em vez de usar cgo. Apenas testado no macOS.
scan <dir|file> — execução simuladaDetecta e relata; não escreve nada.
screenscrub scan ~/Screenshots --ignore 'node_modules/**' --ignore '*.thumb.png'
screenscrub scan shot.png --json # machine-readable report
screenscrub scan ~/Screenshots --preview ~/review # eyeball what clean would redact
--preview <dir> escreve uma cópia não destrutiva de cada imagem sinalizada com cada região detectada delineada (não preenchida), para que você possa confirmar o que clean redigiria antes de confirmar. ⚠️ Essas cópias ainda contêm os segredos — o diretório é criado com 0700 e não deve ser compartilhado.
O relatório termina com um resumo: uma discriminação por tipo e uma lista explícita "Precisa de atenção manual" — arquivos que apresentaram erro, falharam na verificação ou produziram nenhum texto OCR no que deveria ser uma captura de tela (uma falha silenciosa de OCR é lida de forma idêntica a uma imagem limpa, por isso é exibida em vez de ser confiada).
scan --fail-on-findings sai com código não zero quando algo é detectado — uma porta de pre-commit/CI.
clean <dir|file> — extrair, redigir, colocar em quarentenaSCREENSCRUB_PASSPHRASE=… screenscrub clean ~/Screenshots \
--out ~/Screenshots/clean \
--sink file://./secrets.age \
--yes
Para cada imagem com descobertas, clean:
<name>.redacted.png em --out (ou ao lado da fonte),<out>/quarantine/ criptografado com age.Os segredos vão para o destino que você passar em --sink: arquivo age (padrão), 1Password (op://), KeePassXC (keepassxc://), macOS Keychain (keychain://) ou HashiCorp Vault (vault://) — ou destinatários age com suporte de hardware (YubiKey, Touch ID). Veja Destinos de segredos.
Flags:
--exclude-kind e --min-confidence são botões de precisão locais (sem rede) para um corpus ruidoso — a contraparte offline de --llm. Eles trocam recall por um relatório mais silencioso, portanto use-os com cuidado. Ambos também funcionam em scan/verify e podem ser definidos em --config (exclude_kinds, min_confidence).
Ordem de resolução da senha: --passphrase, depois o arquivo de configuração, depois SCREENSCRUB_PASSPHRASE. Evite --passphrase na linha de comando — argv é visível para outros processos.
verify <dir|file> — provar que uma pasta está limpaRe-OCR cada imagem, executa a detecção novamente e sai com código não zero se alguma região de segredo ainda for encontrada — use como uma porta de entrega/CI antes de enviar capturas de tela para um cliente. (Isso prova que o OCR não consegue ler o segredo, não que um humano nunca conseguiria.)
screenscrub verify ~/deliverable/screenshots
echo $? # 0 = limpo, não zero = um segredo ainda é detectável
clean executa essa mesma verificação automaticamente em cada cópia redigida e não colocará em quarentena ou destruirá um original cuja cópia redigida falhe; --no-verify a pula (não recomendado).
open <store> — recuperar segredos ou um original em quarentenaObtenha seus dados de volta: open descriptografa o destino age (os registros de segredos armazenados) ou um original em quarentena para --out, nunca stdout. Veja Decifrando repositórios.
--llm — eliminar falsos positivos benignos com um LLMRegras de nível gitleaks aplicadas a texto OCR (com ruído de OCR) inevitavelmente geram muitos falsos positivos: o detector com viés de recall revela strings benignas de alta entropia que um operador deseja manter — hostnames, GUIDs, SHAs de git, hashes NTLM que são eles próprios a prova de comprometimento. --llm envia cada candidato para Claude (Opus 4.8), mantendo segredos genuínos e descartando os identificadores benignos; regiões confirmadas pelo LLM são preenchidas em vermelho.
ANTHROPIC_API_KEY=… screenscrub clean ~/Screenshots --out ~/clean --llm --yes
Isso quebra o princípio local-e-silencioso propositalmente: ele envia as strings candidatas (os segredos detectados) para a API Anthropic, portanto é apenas opt-in, imprime um aviso e precisa de uma chave de API. Ele falha de forma segura: qualquer erro de API/rede mantém todas as descobertas (redige tudo) em vez de arriscar perder um segredo real. Ele faz uma chamada de API por imagem sinalizada (limitada a 4 concorrentes independentemente de --jobs) — esteja atento aos limites de taxa e custo em diretórios grandes.
--watchscreenscrub clean ~/Screenshots --watch --sink file://./secrets.age --yes
Processa tudo já presente, depois observa novas/imagens modificadas e as limpa à medida que aparecem. Saídas *.redacted.png são ignoradas para evitar loops.
Sobre tokens OCR, screenscrub combina:
AKIA…, GitHub ghp_…, GitLab, Slack, Stripe, OpenAI, chaves de API do GCP, JWTs e cabeçalhos PEM. (Copiado, não importado, para permanecer pequeno e totalmente offline.)postgres://u:p@…), cabeçalhos de autenticação HTTP Basic/Bearer, hashes NetNTLMv2 e NTLM (pwdump), hashes crypt do /etc/shadow, chaves privadas PuTTY e OpenSSH, tokens SendGrid/npm.password, token=, secret, api_key, …) que sinalizam o valor adjacente mesmo quando não corresponde a nenhum padrão e tem baixa entropia.BEGIN/END e redige generosamente toda a região ao redor, em vez de confiar em correspondências por palavra.Cada correspondência mapeia de volta para as caixas de pixels dos tokens que a compõem (duas reconstruções de linha — espaçadas e justas — recuperam segredos que o OCR dividiu por espaços em branco), e as caixas são preenchidas com padding antes do preenchimento.
Um destino é um método: Store(Secret) error. Adaptadores fornecidos:
file:// — arquivo local criptografado com age (padrão). Sem dependências externas; um array JSON de registros criptografados para uma senha scrypt do age (ou para destinatários age — veja criptografia com suporte de hardware abaixo).op:// — 1Password CLI. Executa op item create com o valor na stdin (nunca em argv).keepassxc://./vault.kdbx — KeePassXC. Gerenciador KeePass local multiplataforma popular. Executa keepassxc-cli add; a senha do banco de dados e o segredo são passados na stdin (nunca em argv). A senha do banco de dados é a senha resolvida (--config/SCREENSCRUB_PASSPHRASE).keychain://screenscrub — macOS Keychain. Executa security add-generic-password. Ressalva: a ferramenta security só aceita o segredo como argumento de linha de comando, portanto fica brevemente visível para ps do enquanto está em execução (macOS oculta argv de outros usuários). Para maior higiene, prefira 1Password, KeePassXC ou o destinatário do Secure Enclave abaixo.Para adicionar o seu próprio, implemente sink.SecretSink (internal/sink) e conecte-o em sink.FromURI. Esse é todo o contrato.
O destino de arquivo age padrão e a quarentena podem criptografar para destinatários age em vez de uma senha, incluindo aqueles com suporte de hardware por meio de plugins age:
# YubiKey (PIV) — instale age-plugin-yubikey, então:
screenscrub clean ~/Screenshots --out ~/clean \
--sink file://./secrets.age \
--recipient age1yubikey1q... --yes
# Apple Secure Enclave / Touch ID — instale age-plugin-se, então:
screenscrub clean ~/Screenshots --out ~/clean \
--recipient age1se1q... --yes
# Par de chaves age simples, ou vários destinatários / um arquivo de destinatários:
screenscrub clean ~/Screenshots --recipient age1abc... --recipients-file team.age.pub --yes
No modo de destinatário, nenhuma senha é necessária; tanto o destino quanto a quarentena criptografada vão para os destinatários. Recupere com o token de hardware: age -d -i <identity> secrets.age (a descriptografia solicita o toque do YubiKey / Touch ID). Criptografar para um destinatário plugin precisa do binário age-plugin-<name>; a descriptografia adicionalmente precisa do dispositivo.
openO destino e a quarentena usam chaves separadas derivadas de sua única senha (HKDF), portanto quebrar um repositório não pode abrir o outro. Como as chaves no disco são derivadas, decifre-as com screenscrub em vez de age puro:
screenscrub open ./secrets.age --out secrets.json # os registros de segredos
screenscrub open ~/clean/quarantine/shot.png.ab12cd34.age --out shot.png # um original
open tenta cada chave derivada automaticamente e escreve texto claro em --out (nunca stdout). Para repositórios em modo de destinatário, passe um arquivo de chave age: screenscrub open secrets.<id>.age --identity key.txt --out secrets.json (para YubiKey/Touch ID, use age -d -i diretamente, que aciona o plugin de hardware). O modo de destinatário escreve um arquivo por segredo (secrets.<hash>-<rand>.age) para que execuções repetidas nunca se sobrescrevam.
go test ./... # unit tests run without tesseract; OCR/re-OCR tests skip if absent
A garantia principal é coberta por um teste ponta a ponta: renderizar uma captura de tela com uma chave AWS, executar o pipeline real de OCR → detectar → redigir, re-OCR a saída e afirmar que o segredo desapareceu (internal/redact).
screenscrub não é uma licença para parar de pensar.
password:); um segredo em uma forma que ela não reconhece passará.Ela é ajustada para recall em vez de precisão — deliberadamente gera muitos falsos positivos, porque uma credencial deixada em uma imagem é o caso catastrófico e um falso positivo custa apenas um blob redigido.
Você pode ver um caso de uso realista na pasta testdata/, retirado de capturas de tela do RExpository:
https://jaimepolop.github.io/RExpository/
Não é perfeito, mas é muito bom.
clean re-OCR e executa a detecção novamente; se algum segredo ainda for detectável, ele se recusa a colocar em quarentena ou destruir o original e sinaliza o arquivo. As caixas de redação são preenchidas proporcionalmente à altura do glifo para que bordas suavizadas em texto grande/de alta DPI sejam cobertas.clean escreve a cópia redigida, verifica-a e então move o original não redigido para uma quarentena criptografada com age e o remove de sua localização original — somente após a cópia ser verificada como limpa e os segredos extraídos estarem seguros no disco. Etapas destrutivas exigem --yes.AK****…**LE). A única cópia em texto claro reside na memória até chegar ao seu destino criptografado.--purge e os limites da exclusão seguraPor padrão, os originais são mantidos (criptografados) em quarentena para que uma redação falsa seja recuperável. --purge em vez disso destrói o original: sobrescreve com aleatório, sobrescreve com zeros e depois remove o link.
A exclusão segura não é confiável. Em SSDs, sistemas de arquivos copy-on-write (APFS, Btrfs, ZFS), sistemas de arquivos com journal e volumes com snapshots/backup, sobrescrever e depois remover o link não garante que os bytes originais desapareceram — nivelamento de desgaste e snapshots podem retê-los. A única defesa real é nunca persistir texto claro em primeiro lugar. Prefira a quarentena criptografada e trate --purge como um esforço de melhor esforço.
OCR — uma possível adição futura.Padrões de credenciais fornecidos derivam de gitleaks (MIT).
![]() |
| Flag | Significado |
|---|
--out <dir> | onde as cópias redigidas vão (padrão: ao lado da fonte) |
--sink <uri> | file://./secrets.age · op://Vault · keepassxc://./v.kdbx · keychain://service · vault://KV |
--recipient <age1…> | criptografar para um destinatário age (repetível): X25519, YubiKey ou Touch ID |
--recipients-file <f> | arquivo de destinatários age, um por linha |
--passphrase | senha para o destino de arquivo / quarentena (prefira a variável de ambiente ou --config) |
--watch | continuar em execução; processar novas capturas de tela à medida que aparecem (fsnotify) |
--purge | destruir os originais em vez de colocar em quarentena (veja ressalva abaixo) |
--keep-originals | escrever apenas cópias redigidas; deixar os originais no lugar (sem quarentena, sem senha, sem --yes) — mutuamente exclusivo com --purge |
--yes | confirmar o tratamento destrutivo dos originais (obrigatório) |
--config <file> | config JSON; permite manter a senha fora do argv |
--no-verify | pular a verificação re-OCR da saída redigida (não recomendado) |
-i, --interactive | abrir cada imagem sinalizada e confirmar redigir/pular antes de tocá-la |
--llm | usar Claude para eliminar falsos positivos benignos; envia strings candidatas para a API Anthropic |
--llm-api-key | chave da API Anthropic para --llm (ou ANTHROPIC_API_KEY) |
--jobs N | processar N imagens em paralelo (padrão: número de CPUs) |
--manifest <file> | escrever um mapa JSON de original→redigido→regiões |
--ocr-raw / --ocr-upscale N | desativar o pré-processamento de terminal escuro / aumentar a escala antes do OCR |
--exclude-kind <kind> | suprimir descobertas de um tipo (repetível), ex. high-entropy — botão de precisão offline |
--min-confidence <0..1> | descartar descobertas abaixo desta confiança — botão de precisão offline |
--json | emitir o relatório como JSON |
--ignore <glob> | ignorar glob, repetível; ** corresponde a qualquer profundidade |
--lang | idioma do tesseract (padrão eng) |
vault:// — HashiCorp Vault KV v2 — presente, mas adiado/menos comum; o caminho faz hash do segredo para que nunca apareça na URL.