
Encuentra credenciales en capturas de pantalla, guárdalas en tu gestor de secretos y redáctalas irreversiblemente de la imagen — local, sin conexión, basado en OCR.
Encuentra credenciales en capturas de pantalla, extráelas a un gestor de secretos y
redáctelas irreversiblemente de las imágenes — para que los secretos no se pudran en tus
notas, capturas de pantalla y ~/Desktop.
Local. Fuera de línea. Silencioso. Sin red, sin telemetría por defecto.
screenscrub une dos categorías de herramientas existentes: los redactores GUI (Xnapper, Shhshot, macshot) difuminan datos personales a mano pero no distinguen un secreto de una dirección ni capturan lo que ocultan; los escáneres de secretos (gitleaks, TruffleHog) encuentran credenciales reales pero solo en texto, nunca en un PNG. screenscrub trae detección de nivel gitleaks — más un pack de equipo rojo y análisis de entropía — a píxeles, sin interfaz gráfica: captura cada secreto en tu gestor, redacta la imagen irreversiblemente, y vuelve a aplicar OCR al resultado para demostrar que el secreto ha desaparecido.
Una red de seguridad, no una garantía — screenscrub nunca afirma que una imagen esté "garantizada como limpia"; tanto el OCR como la detección fallan en cosas, así que siempre revisa los resultados. Lee esto antes de confiar en él.
screenscrub scan ~/Screenshots # simulación: detecta + reporta, no escribe nada
screenscrub clean ~/Screenshots --out ~/clean \ # extrae, redacta, verifica, pone en cuarentena
--sink file://./secrets.age --yes
screenscrub verify ~/clean # demuestra que una carpeta está limpia (código de salida ≠0 si no lo está)
screenscrub clean --keep-originals ./tmp
Limpieza de 7 imagen(es) en ./tmp (7 trabajador(es))…
[1/7] tmp/ssh.jpg — 6 región(es) → tmp/ssh.redacted.jpg ✓ verificado
[2/7] tmp/key.jpg — 13 región(es) → tmp/key.redacted.jpg ✓ verificado
[3/7] tmp/test2.jpg — 12 región(es) → tmp/test2.redacted.jpg ✓ verificado
[4/7] tmp/test5.jpg — 13 región(es) → tmp/test5.redacted.jpg ✓ verificado
[5/7] tmp/test3.jpg — error: falló la verificación: 4 región(es) de secreto aún detectables en tmp/test3.redacted.jpg; original conservado
[6/7] tmp/test4.jpg — error: falló la verificación: 9 región(es) de secreto aún detectables en tmp/test4.redacted.jpg; original conservado
[7/7] tmp/test6.jpg — error: falló la verificación: 7 región(es) de secreto aún detectables en tmp/test6.redacted.jpg; original conservado
7/7 archivo(s) marcados, 382 región(es) en total, 3 archivo(s) con error/sin verificar.
Por tipo:
high-entropy 343
keyword-secret 15
db-connection-uri 10
github-pat 6
unix-crypt-hash 6
aws-access-key 1
stripe-key 1
Necesita atención manual (3):
tmp/test3.jpg — error: falló la verificación: 4 región(es) de secreto aún detectables en tmp/test3.redacted.jpg; original conservado
tmp/test4.jpg — error: falló la verificación: 9 región(es) de secreto aún detectables en tmp/test4.redacted.jpg; original conservado
tmp/test6.jpg — error: falló la verificación: 7 región(es) de secreto aún detectables en tmp/test6.redacted.jpg; original conservado
| Antes — valores secretos visibles | Después de clean — redactado irreversiblemente |
![]() |
La misma captura de pantalla antes y después de clean: cada valor secreto se decodifica a píxeles, se encuadra y se recodifica a una nueva imagen (irreversible) — las etiquetas y la estructura siguen siendo legibles.
Requiere el binario de OCR tesseract en PATH:
brew install tesseractwinget install UB-Mannheim.TesseractOCR (o el instalador de UB Mannheim)apt-get install tesseract-ocrLuego construye:
go build -o screenscrub ./cmd/screenscrub
Se compila de forma cruzada para macOS (arm64/amd64) y Windows (amd64) — usa tesseract mediante subprocesos en lugar de cgo. Solo probado en macOS.
scan <directorio|archivo> — simulaciónDetecta y reporta; no escribe nada.
screenscrub scan ~/Screenshots --ignore 'node_modules/**' --ignore '*.thumb.png'
screenscrub scan shot.png --json # informe legible por máquina
screenscrub scan ~/Screenshots --preview ~/review # revisa visualmente lo que clean redactaría
--preview <directorio> escribe una copia no destructiva de cada imagen marcada con cada
región detectada delineada (no rellena), para que puedas confirmar lo que clean
redactaría antes de comprometerte. ⚠️ Estas copias aún contienen los secretos — el directorio
se crea con permisos 0700 y no debe compartirse.
El informe termina con un resumen: un desglose por tipo y una lista explícita de "Necesita atención manual" — archivos que dieron error, fallaron en la verificación o no produjeron ningún texto OCR en lo que debería ser una captura de pantalla (un fallo silencioso del OCR se lee igual que una imagen limpia, por lo que se muestra en lugar de confiar en ello).
scan --fail-on-findings sale con código distinto de cero cuando se detecta algo — una puerta de
pre-commit/CI.
clean <directorio|archivo> — extrae, redacta, pone en cuarentenaSCREENSCRUB_PASSPHRASE=… screenscrub clean ~/Screenshots \
--out ~/Screenshots/clean \
--sink file://./secrets.age \
--yes
Para cada imagen con hallazgos, clean:
<nombre>.redacted.png en --out (o junto al original),<out>/quarantine/ cifrado con age.Los secretos van al sumidero que pases con --sink: archivo age (por defecto), 1Password
(op://), KeePassXC (keepassxc://), Llavero de macOS (keychain://), o
HashiCorp Vault (vault://) — o destinatarios age respaldados por hardware (YubiKey,
Touch ID). Consulta Sumideros de secretos.
Banderas:
--exclude-kind y --min-confidence son perillas de precisión locales (sin red)
para un corpus ruidoso — la contraparte sin conexión de --llm. Intercambian exhaustividad por un
informe más silencioso, así que úsalos con cuidado. Ambos
también funcionan en scan/verify y se pueden configurar en --config (exclude_kinds,
min_confidence).
Orden de resolución de la frase de contraseña: --passphrase, luego el archivo de configuración, luego
SCREENSCRUB_PASSPHRASE. Evita --passphrase en la línea de comandos — argv es
visible para otros procesos.
verify <directorio|archivo> — demuestra que una carpeta está limpiaVuelve a aplicar OCR a cada imagen, vuelve a ejecutar la detección y sale con código distinto de cero si todavía se encuentra alguna región secreta — úsalo como puerta de entrega/CI antes de enviar capturas de pantalla a un cliente. (Prueba que el OCR no puede leer el secreto, no que un humano nunca podría.)
screenscrub verify ~/deliverable/screenshots
echo $? # 0 = limpio, distinto de cero = un secreto aún es detectable
clean ejecuta esta misma comprobación automáticamente en cada copia redactada y no pondrá en cuarentena ni destruirá un original cuya copia redactada falle; --no-verify la omite (no recomendado).
open <almacén> — recupera secretos o un original en cuarentenaRecupera tus datos: open descifra el sumidero age (los registros de secretos almacenados) o un original en cuarentena hacia --out, nunca a stdout. Consulta Descifrar almacenes.
--llm — elimina falsos positivos benignos con un LLMLas reglas de nivel gitleaks sobre texto OCR (con ruido de OCR) inevitablemente generan falsos positivos: el detector sesgado hacia la exhaustividad resalta cadenas de alta entropía benignas que un operador quiere conservar — nombres de host, GUIDs, SHAs de git, hashes NTLM que son en sí mismos la prueba de compromiso. --llm pasa cada candidato ante Claude (Opus 4.8), conservando los secretos genuinos y descartando los identificadores benignos; las regiones confirmadas por LLM se rellenan en rojo.
ANTHROPIC_API_KEY=… screenscrub clean ~/Screenshots --out ~/clean --llm --yes
Esto rompe intencionalmente la premisa de local-y-silencioso: envía las cadenas candidatas (los secretos detectados) a la API de Anthropic, por lo que es solo opt-in, imprime una advertencia y necesita una clave API. Falla de forma segura: cualquier error de API/red mantiene todos los hallazgos (redacta todo) en lugar de arriesgarse a perder un secreto real. Realiza una llamada API por imagen marcada (máximo 4 concurrentes independientemente de --jobs) — ten en cuenta los límites de tasa y el costo en directorios grandes.
--watchscreenscrub clean ~/Screenshots --watch --sink file://./secrets.age --yes
Procesa todo lo ya presente, luego observa nuevas imágenes modificadas y limpia cada una a medida que aparece. Se ignoran las salidas *.redacted.png para evitar bucles.
Sobre los tokens de OCR, screenscrub combina:
AKIA…, GitHub ghp_…, GitLab, Slack, Stripe, OpenAI, claves API de GCP, JWT y cabeceras PEM. (Copiadas, no importadas, para mantenerse pequeño y completamente fuera de línea).postgres://u:p@…), cabeceras de autenticación HTTP Basic/Bearer, hashes NetNTLMv2 y NTLM (pwdump), hashes crypt de /etc/shadow, claves privadas PuTTY y OpenSSH, tokens de SendGrid/npm.password, token=, secret, api_key, …) que marcan el valor adyacente incluso cuando no coincide con ningún patrón y tiene baja entropía.BEGIN/END y redacta generosamente toda la región circundante en lugar de confiar en coincidencias palabra por palabra.Cada acierto se asigna a las cajas de píxeles de los tokens que lo componen (dos reconstrucciones de línea — espaciada y ajustada — recuperan secretos que el OCR dividió por espacios en blanco), y las cajas se rellenan con un margen antes de rellenar.
Un sumidero es un método: Store(Secret) error. Adaptadores incluidos:
file:// — archivo local cifrado con age (por defecto). Sin dependencias externas; un array JSON de registros cifrados con una frase de contraseña scrypt de age (o con destinatarios age — consulta cifrado respaldado por hardware abajo).op:// — CLI de 1Password. Ejecuta op item create con el valor en stdin (nunca en argv).keepassxc://./vault.kdbx — KeePassXC. Gestor KeePass local multiplataforma popular. Ejecuta keepassxc-cli add; la contraseña de la base de datos y el secreto se pasan ambos por stdin (nunca en argv). La contraseña de la base de datos es la frase de contraseña resuelta (--config/SCREENSCRUB_PASSPHRASE).keychain://screenscrub — Llavero de macOS. Ejecuta security add-generic-password. Advertencia: la herramienta security solo acepta el secreto como argumento de línea de comandos, por lo que es brevemente visible para ps del mientras se ejecuta (macOS oculta argv de otros usuarios). Para una higiene más estricta prefiere 1Password, KeePassXC o el destinatario del Enclave Seguro a continuación.Para añadir el tuyo propio, implementa sink.SecretSink (internal/sink) y conéctalo en sink.FromURI. Ese es todo el contrato.
El sumidero de archivo age por defecto y la cuarentena pueden cifrar para destinatarios age en lugar de una frase de contraseña, incluidos los respaldados por hardware mediante complementos de age:
# YubiKey (PIV) — instala age-plugin-yubikey, luego:
screenscrub clean ~/Screenshots --out ~/clean \
--sink file://./secrets.age \
--recipient age1yubikey1q... --yes
# Apple Secure Enclave / Touch ID — instala age-plugin-se, luego:
screenscrub clean ~/Screenshots --out ~/clean \
--recipient age1se1q... --yes
# Par de claves age normal, o varios destinatarios / un archivo de destinatarios:
screenscrub clean ~/Screenshots --recipient age1abc... --recipients-file team.age.pub --yes
En modo destinatario no se necesita frase de contraseña; tanto el sumidero como la cuarentena cifrada van a los destinatarios. Recupera con el token de hardware: age -d -i <identidad> secrets.age (el descifrado solicita el toque de YubiKey / Touch ID). Cifrar a un destinatario de complemento necesita el binario age-plugin-<nombre>; el descifrado además necesita el dispositivo.
openEl sumidero y la cuarentena usan claves separadas derivadas de tu única frase de contraseña (HKDF), por lo que descifrar un almacén no puede abrir el otro. Dado que las claves en disco se derivan, descífralas con screenscrub en lugar de con age directamente:
screenscrub open ./secrets.age --out secrets.json # los registros de secretos
screenscrub open ~/clean/quarantine/shot.png.ab12cd34.age --out shot.png # un original
open prueba cada clave derivada automáticamente y escribe el texto plano en --out (nunca a stdout). Para almacenes en modo destinatario, pasa un archivo de clave age: screenscrub open secrets.<id>.age --identity key.txt --out secrets.json (para YubiKey/Touch ID usa age -d -i directamente, que gestiona el complemento de hardware). El modo destinatario escribe un archivo por secreto (secrets.<hash>-<rand>.age) para que las ejecuciones repetidas nunca se sobrescriban entre sí.
go test ./... # las pruebas unitarias se ejecutan sin tesseract; las pruebas de OCR/re-OCR se omiten si no está presente
La garantía principal se cubre con una prueba de extremo a extremo: renderiza una captura de pantalla con una clave AWS, ejecuta el pipeline real de OCR → detectar → redactar, vuelve a aplicar OCR a la salida y afirma que el secreto ha desaparecido (internal/redact).
screenscrub no es una licencia para dejar de pensar.
password:); un secreto en una forma que no reconoce pasará desapercibido.Está ajustado para mayor exhaustividad que precisión — deliberadamente genera falsos positivos, porque una credencial que queda en una imagen es el caso catastrófico y un falso positivo solo te cuesta un bloque redactado.
Puedes ver un caso de uso realista en la carpeta testdata/, tomada de capturas de pantalla de RExpository:
https://jaimepolop.github.io/RExpository/
No es perfecto, pero bastante bueno.
clean vuelve a aplicar OCR y vuelve a ejecutar la detección; si algún secreto sigue siendo detectable se niega a poner en cuarentena o destruir el original y marca el archivo. Las cajas de redacción se rellenan proporcionalmente a la altura del glifo para que los bordes suavizados en texto grande/de alta DPI queden cubiertos.clean escribe la copia redactada, la verifica, luego mueve el original sin redactar a una cuarentena cifrada con age y lo elimina de su ubicación original — solo después de que la copia esté verificada como limpia y los secretos extraídos estén seguros en disco. Los pasos destructivos requieren --yes.AK****…**LE). La única copia en texto plano vive en memoria hasta que llega a tu sumidero cifrado.--purge y los límites de la eliminación seguraPor defecto, los originales se conservan (cifrados) en cuarentena para que una redacción falsa sea recuperable. --purge en su lugar destruye el original: sobrescribe con aleatorios, sobrescribe con ceros, luego desvincula.
La eliminación segura no es fiable. En SSDs, sistemas de archivos copy-on-write (APFS, Btrfs, ZFS), sistemas de archivos con journal y volúmenes con instantáneas/respaldos, sobrescribir y luego desvincular no garantiza que los bytes originales hayan desaparecido — el nivelado de desgaste y las instantáneas pueden retenerlos. La única defensa real es no persistir nunca texto plano en primer lugar. Prefiere la cuarentena cifrada y trata --purge como un esfuerzo de buena fe.
OCR — una posible adición futura.Los patrones de credenciales incorporados derivan de gitleaks (MIT).
![]() |
| Banderas | Significado |
|---|
--out <directorio> | dónde van las copias redactadas (por defecto: junto al original) |
--sink <uri> | file://./secrets.age · op://Vault · keepassxc://./v.kdbx · keychain://servicio · vault://KV |
--recipient <age1…> | cifrar para un destinatario age (repetible): X25519, YubiKey o Touch ID |
--recipients-file <archivo> | archivo con destinatarios age, uno por línea |
--passphrase | frase de contraseña para el sumidero de archivo / cuarentena (prefiere la variable de entorno o --config) |
--watch | mantenerse ejecutando; procesa nuevas capturas a medida que llegan (fsnotify) |
--purge | destruye los originales en lugar de ponerlos en cuarentena (ver advertencia abajo) |
--keep-originals | escribe solo copias redactadas; deja los originales en su lugar (sin cuarentena, sin frase de contraseña, sin --yes) — mutuamente excluyente con --purge |
--yes | confirma el manejo destructivo de originales (requerido) |
--config <archivo> | configuración JSON; permite mantener la frase de contraseña fuera de argv |
--no-verify | saltar la verificación con re-OCR de la salida redactada (no recomendado) |
-i, --interactive | abrir cada imagen marcada y confirmar redactar/saltar antes de tocarla |
--llm | usar Claude para eliminar falsos positivos benignos; envía cadenas candidatas a la API de Anthropic |
--llm-api-key | clave API de Anthropic para --llm (o ANTHROPIC_API_KEY) |
--jobs N | procesa N imágenes en paralelo (por defecto: número de CPUs) |
--manifest <archivo> | escribe un mapa JSON de original→redactado→regiones |
--ocr-raw / --ocr-upscale N | deshabilitar preprocesamiento de terminal oscura / aumentar resolución antes de OCR |
--exclude-kind <tipo> | suprimir hallazgos de un tipo (repetible), ej. high-entropy — perilla de precisión sin conexión |
--min-confidence <0..1> | descartar hallazgos por debajo de esta confianza — perilla de precisión sin conexión |
--json | emitir el informe como JSON |
--ignore <glob> | ignorar glob, repetible; ** coincide con cualquier profundidad |
--lang | idioma de tesseract (por defecto eng) |
vault:// — HashiCorp Vault KV v2 — presente pero diferido/menos común; la ruta hashea el secreto para que nunca aparezca en la URL.