
Найдите учетные данные на скриншотах, сохраните их в ваш менеджер секретов и безвозвратно заретушируйте их на изображении — локально, офлайн, на основе OCR.
Ищет учетные данные на скриншотах, извлекает их в менеджер секретов и безвозвратно редактирует их на изображениях — чтобы секреты не гнили в ваших заметках, скриншотах и ~/Desktop.
Локальный. Офлайн. Бесшумный. По умолчанию без сети и телеметрии.
screenscrub объединяет две существующие категории инструментов: GUI-редакторы (Xnapper, Shhshot, macshot) вручную размывают PII, но не отличают секрет от адреса и не фиксируют, что они скрывают; сканеры секретов (gitleaks, TruffleHog) находят настоящие учетные данные, но только в тексте, никогда в PNG. screenscrub переносит обнаружение уровня gitleaks — плюс red-team-пакет и энтропийный анализ — на пиксели, безголово: он захватывает каждый секрет в ваш менеджер, безвозвратно редактирует изображение и повторно выполняет OCR, чтобы доказать, что секрет исчез.
Подстраховка, а не гарантия — screenscrub никогда не утверждает, что изображение «гарантированно чистое»; OCR и обнаружение оба что-то пропускают, поэтому всегда проверяйте результаты глазами. Прочтите это, прежде чем полагаться на него.
screenscrub scan ~/Screenshots # сухой прогон: обнаружить + отчёт, без записи
screenscrub clean ~/Screenshots --out ~/clean \ # извлечь, отредактировать, проверить, поместить в карантин
--sink file://./secrets.age --yes
screenscrub verify ~/clean # доказать, что папка чиста (выход≠0, если нет)
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
| До — секретные значения видны | После clean — безвозвратно отредактировано |
![]() |
Один и тот же скриншот до и после clean: каждое секретное значение декодируется в пиксели, обрамляется и перекодируется в новое изображение (безвозвратно) — подписи и структура остаются читаемыми.
Требуется бинарник OCR tesseract в PATH:
brew install tesseractwinget install UB-Mannheim.TesseractOCR (или установщик UB Mannheim)apt-get install tesseract-ocrЗатем сборка:
go build -o screenscrub ./cmd/screenscrub
Кросскомпилируется для macOS (arm64/amd64) и Windows (amd64) — он вызывает tesseract через оболочку, а не через cgo. Протестировано только на macOS.
scan <dir|file> — сухой прогонОбнаруживает и сообщает; ничего не записывает.
screenscrub scan ~/Screenshots --ignore 'node_modules/**' --ignore '*.thumb.png'
screenscrub scan shot.png --json # машинно-читаемый отчёт
screenscrub scan ~/Screenshots --preview ~/review # просмотреть, что clean отредактирует
--preview <dir> записывает неразрушающую копию каждого помеченного изображения с контуром (не заливкой) каждой обнаруженной области, чтобы вы могли подтвердить, что clean отредактирует, прежде чем фиксировать. ⚠️ Эти копии всё ещё содержат секреты — каталог создаётся с правами 0700 и не подлежит распространению.
Отчёт завершается сводкой: разбивка по типам и явный список «Требует ручного внимания» — файлы, которые завершились ошибкой, не прошли проверку или не содержат текста OCR на том, что должно быть скриншотом (молчаливый пропуск OCR читается так же, как чистое изображение, поэтому он отображается, а не доверяется).
scan --fail-on-findings завершается с ненулевым кодом, если что-то обнаружено — шлюз для pre-commit/CI.
clean <dir|file> — извлечение, редактирование, карантинSCREENSCRUB_PASSPHRASE=… screenscrub clean ~/Screenshots \
--out ~/Screenshots/clean \
--sink file://./secrets.age \
--yes
Для каждого изображения с находками clean:
<name>.redacted.png в --out (или рядом с исходным),<out>/quarantine/, зашифрованное с помощью age.Секреты попадают в тот приёмник, который вы передаёте в --sink: файл age (по умолчанию), 1Password (op://), KeePassXC (keepassxc://), связка ключей macOS (keychain://) или HashiCorp Vault (vault://) — или аппаратно-поддерживаемые получатели age (YubiKey, Touch ID). См. Приёмники секретов.
Флаги:
--exclude-kind и --min-confidence — локальные (без сети) ручки точности для шумного корпуса; офлайн-аналог --llm. Они обменивают полноту на более тихий отчёт, поэтому используйте их осознанно. Они также работают с scan/verify и могут быть заданы в --config (exclude_kinds, min_confidence).
Порядок разрешения парольной фразы: --passphrase, затем файл конфигурации, затем SCREENSCRUB_PASSPHRASE. Избегайте --passphrase в командной строке — argv виден другим процессам.
verify <dir|file> — доказать, что папка чистаПовторно выполняет OCR каждого изображения, повторно запускает обнаружение и завершается с ненулевым кодом, если всё ещё найдена какая-либо секретная область — используйте это как контрольную точку / шлюз CI перед отправкой скриншотов клиенту. (Он доказывает, что OCR не может прочитать секрет, а не то, что человек никогда не сможет.)
screenscrub verify ~/deliverable/screenshots
echo $? # 0 = чисто, ненулевое = секрет всё ещё обнаруживается
clean автоматически выполняет эту же проверку для каждой отредактированной копии и не помещает в карантин и не уничтожает оригинал, если его отредактированная копия не прошла проверку; --no-verify пропускает её (не рекомендуется).
open <store> — восстановить секреты или оригинал из карантинаВернуть свои данные: open расшифровывает приёмник age (сохранённые записи секретов) или оригинал из карантина в --out, никогда в stdout. См. Расшифровка хранилищ.
--llm — отбрасывать безобидные ложные срабатывания с помощью LLMПравила уровня gitleaks над текстом OCR (с шумом OCR) неизбежно дают ложные срабатывания: обнаружение с наклоном в полноту выявляет безобидные строки с высокой энтропией, которые оператор хочет оставить — имена хостов, GUID, SHA из git, NTLM-хэши, которые сами по себе являются доказательством компрометации. --llm пропускает каждого кандидата через Claude (Opus 4.8), сохраняя настоящие секреты и отбрасывая безобидные идентификаторы; области, подтверждённые LLM, заливаются красным.
ANTHROPIC_API_KEY=… screenscrub clean ~/Screenshots --out ~/clean --llm --yes
Это намеренно нарушает локальность и тишину: он отправляет строки-кандидаты (обнаруженные секреты) в Anthropic API, поэтому он только по желанию, выводит предупреждение и требует ключ API. Он отказывает в закрытом режиме: любая ошибка API/сети сохраняет все находки (редактирует всё), а не рискует пропустить настоящий секрет. Он делает один вызов API для каждого помеченного изображения (максимум 4 одновременных, независимо от --jobs) — учитывайте ограничения скорости и стоимость для больших каталогов.
--watchscreenscrub clean ~/Screenshots --watch --sink file://./secrets.age --yes
Обрабатывает всё, что уже присутствует, затем следит за новыми/изменёнными изображениями и редактирует каждое по мере его появления. Выходные файлы *.redacted.png игнорируются для предотвращения циклов.
Поверх токенов OCR screenscrub объединяет:
AKIA…, GitHub ghp_…, GitLab, Slack, Stripe, OpenAI, GCP API-ключи, JWT и заголовки PEM. (Скопированы, не импортированы, чтобы оставаться компактными и полностью офлайн.)postgres://u:p@…), HTTP-заголовки Basic/Bearer аутентификации, хэши NetNTLMv2 и NTLM (pwdump), криптографические хэши /etc/shadow, закрытые ключи PuTTY и OpenSSH, токены SendGrid/npm.password, token=, secret, api_key, …), которые помечают смежное значение, даже если оно не соответствует ни одному шаблону и имеет низкую энтропию.BEGIN/END и щедро редактирует всю охватывающую область, а не полагается на совпадения по словам.Каждое совпадение сопоставляется с пиксельными рамками токенов, из которых оно состоит (две реконструкции строк — с пробелами и без — восстанавливают секреты, которые OCR разделила по пробелам), и рамки заполняются с отступом перед заливкой.
Приёмник — один метод: Store(Secret) error. Поставляемые адаптеры:
file:// — age-зашифрованный локальный файл (по умолчанию). Без внешних зависимостей; JSON-массив записей, зашифрованных для парольной фразы age scrypt (или для получателей age — см. аппаратно-поддерживаемое шифрование ниже).op:// — 1Password CLI. Выполняет op item create со значением на stdin (никогда в argv).keepassxc://./vault.kdbx — KeePassXC. Популярный локальный менеджер паролей с поддержкой кроссплатформенности. Выполняет keepassxc-cli add; пароль базы данных и секрет передаются на stdin (никогда в argv). Пароль базы данных — это разрешённая парольная фраза (--config/SCREENSCRUB_PASSPHRASE).keychain://screenscrub — связка ключей macOS. Выполняет security add-generic-password. Предостережение: инструмент security принимает секрет только как аргумент командной строки, поэтому он ненадолго видим в ps для того же пользователя во время выполнения (macOS скрывает argv от других пользователей). Для более строгой гигиены предпочитайте 1Password, KeePassXC или получатель Secure Enclave ниже.Чтобы добавить свой, реализуйте sink.SecretSink (internal/sink) и подключите его в sink.FromURI. Это весь контракт.
Приёмник файла age по умолчанию и карантин могут шифровать для получателей age вместо парольной фразы, включая аппаратно-поддерживаемые через плагины age:
# YubiKey (PIV) — установите age-plugin-yubikey, затем:
screenscrub clean ~/Screenshots --out ~/clean \
--sink file://./secrets.age \
--recipient age1yubikey1q... --yes
# Apple Secure Enclave / Touch ID — установите age-plugin-se, затем:
screenscrub clean ~/Screenshots --out ~/clean \
--recipient age1se1q... --yes
# Обычная пара ключей age, или несколько получателей / файл получателей:
screenscrub clean ~/Screenshots --recipient age1abc... --recipients-file team.age.pub --yes
В режиме получателя парольная фраза не нужна; и приёмник, и зашифрованный карантин отправляются получателям. Восстановление с помощью аппаратного токена: age -d -i <identity> secrets.age (расшифровка запрашивает прикосновение к YubiKey / Touch ID). Шифрование для получателя-плагина требует бинарника age-plugin-<name>; расшифровка дополнительно требует устройства.
openПриёмник и карантин используют отдельные ключи, производные от вашей одной парольной фразы (HKDF), поэтому взлом одного хранилища не может открыть другое. Поскольку ключи на диске являются производными, расшифровывайте их с помощью screenscrub, а не сырого age:
screenscrub open ./secrets.age --out secrets.json # записи секретов
screenscrub open ~/clean/quarantine/shot.png.ab12cd34.age --out shot.png # оригинал
open автоматически пробует каждый производный ключ и записывает открытый текст в --out (никогда в stdout). Для хранилищ в режиме получателя передайте файл ключа age: screenscrub open secrets.<id>.age --identity key.txt --out secrets.json (для YubiKey/Touch ID используйте age -d -i напрямую, что управляет аппаратным плагином). Режим получателя записывает один файл на секрет (secrets.<hash>-<rand>.age), поэтому повторные запуски никогда не перезаписывают друг друга.
go test ./... # модульные тесты выполняются без tesseract; тесты OCR/повторного OCR пропускаются, если он отсутствует
Гарантия заголовка покрывается сквозным тестом: отрисовка скриншота с AWS-ключом, запуск реального конвейера OCR → обнаружение → редактирование, повторный OCR вывода и проверка, что секрет исчез (internal/redact).
screenscrub — это не лицензия перестать думать.
password:); секрет в форме, которую он не распознаёт, будет пропущен.Он настроен на полноту, а не на точность — он намеренно даёт ложные срабатывания, потому что оставленные в изображении учётные данные — это катастрофический случай, а ложное срабатывание стоит только залитого блоба.
Вы можете увидеть реалистичный вариант использования в папке testdata/, взятый со скриншотов RExpository:
https://jaimepolop.github.io/RExpository/
Это не идеально, но довольно хорошо.
clean повторно выполняет OCR и повторно запускает обнаружение; если какой-либо секрет всё ещё обнаруживается, он отказывается помещать в карантин или уничтожать оригинал и помечает файл. Поля редактирования дополняются пропорционально высоте глифов, чтобы покрыть края с антиалиасингом на большом / высоком DPI тексте.clean записывает отредактированную копию, проверяет её, затем перемещает неотредактированный оригинал в зашифрованный age карантин и удаляет его из исходного местоположения — только после того, как копия проверена как чистая, а извлечённые секреты безопасно сохранены на диске. Разрушительные шаги требуют --yes.AK****…**LE). Единственная копия в открытом виде существует в памяти до тех пор, пока не попадёт в ваш зашифрованный приёмник.--purge и ограничения безопасного удаленияПо умолчанию оригиналы сохраняются (зашифрованные) в карантине, чтобы ложное редактирование можно было восстановить. --purge вместо этого уничтожает оригинал: перезапись случайными данными, перезапись нулями, затем удаление.
Безопасное удаление ненадёжно. На SSD, файловых системах с копированием при записи (APFS, Btrfs, ZFS), журналируемых файловых системах и снапшотированных/резервируемых томах перезапись с последующим удалением не гарантирует, что исходные байты исчезли — выравнивание износа и снимки могут их сохранить. Единственная настоящая защита — никогда не сохранять открытый текст в первую очередь. Предпочитайте зашифрованный карантин и относитесь к --purge как к попытке с наилучшим возможным результатом.
OCR — возможное будущее дополнение.Встроенные шаблоны учётных данных происходят из gitleaks (MIT).
![]() |
| Флаг | Значение |
|---|
--out <dir> | куда помещать отредактированные копии (по умолчанию: рядом с исходным) |
--sink <uri> | file://./secrets.age · op://Vault · keepassxc://./v.kdbx · keychain://service · vault://KV |
--recipient <age1…> | зашифровать для получателя age (повторяемый): X25519, YubiKey или Touch ID |
--recipients-file <f> | файл с получателями age, по одному в строке |
--passphrase | парольная фраза для файлового приёмника / карантина (предпочтительно использовать переменную окружения или --config) |
--watch | продолжать работу; обрабатывать новые скриншоты по мере их появления (fsnotify) |
--purge | уничтожить оригиналы вместо помещения в карантин (см. предостережение ниже) |
--keep-originals | записывать только отредактированные копии; оставить оригиналы на месте (без карантина, без парольной фразы, без --yes) — взаимно исключает --purge |
--yes | подтвердить разрушительную обработку оригиналов (обязательно) |
--config <file> | JSON-конфигурация; позволяет хранить парольную фразу вне argv |
--no-verify | пропустить повторную OCR-проверку отредактированного вывода (не рекомендуется) |
-i, --interactive | открывать каждое помеченное изображение и подтверждать редактирование/пропуск перед его обработкой |
--llm | использовать Claude для отбрасывания безобидных ложных срабатываний; отправляет строки-кандидаты в Anthropic API |
--llm-api-key | ключ API Anthropic для --llm (или ANTHROPIC_API_KEY) |
--jobs N | обрабатывать N изображений параллельно (по умолчанию: количество ядер CPU) |
--manifest <file> | записать JSON-карту оригинальное→отредактированное→области |
--ocr-raw / --ocr-upscale N | отключить предобработку тёмного терминала / увеличить перед OCR |
--exclude-kind <kind> | подавить находки определённого типа (повторяемый), например high-entropy — офлайн-ручка точности |
--min-confidence <0..1> | отбрасывать находки ниже этого порога достоверности — офлайн-ручка точности |
--json | выводить отчёт в формате JSON |
--ignore <glob> | игнорировать glob, повторяемый; ** соответствует любой вложенности |
--lang | язык Tesseract (по умолчанию eng) |
vault:// — HashiCorp Vault KV v2 — присутствует, но отложен/менее распространён; путь хэширует секрет, поэтому он никогда не появляется в URL.