在截图中查找凭据,将其提取到密钥管理器,并不可逆转地将它们从图像中隐去——这样秘密就不会在你的笔记、截图和 ~/Desktop 中腐烂。
本地。离线。静默。默认无网络、无遥测。
screenscrub 连接了两个现有的工具类别:GUI 隐去工具(Xnapper、Shhshot、macshot)手动模糊 PII,但无法区分秘密与地址,也无法捕捉它们隐藏的内容;秘密扫描器(gitleaks、TruffleHog)能发现真实的凭据,但仅限于文本,从未出现在 PNG 中。screenscrub 将 gitleaks 级别的检测——外加红队包和熵分析——引入像素图,以无头方式执行:它将每个秘密捕获到你的管理器,不可逆地隐去图像,并重新 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 前后:每个秘密值被解码为像素、框定并重新编码到新图像中(不可逆)——标签和结构保持可读。
需要 tesseract OCR 二进制文件在 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 在检测到任何内容时退出码非零——用于预提交/CI 门控。
clean <dir|file> — 提取、隐去、隔离SCREENSCRUB_PASSPHRASE=… screenscrub clean ~/Screenshots \
--out ~/Screenshots/clean \
--sink file://./secrets.age \
--yes
对于每个有发现的图像,clean:
<name>.redacted.png 写入 --out(或源文件旁),<out>/quarantine/。秘密被传递给你通过 --sink 指定的 sink:age 文件(默认)、1Password(op://)、KeePassXC(keepassxc://)、macOS Keychain(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、Git SHA、NTLM 哈希(这些哈希本身就是妥协的证据)。--llm 将每个候选字符串传递给 Claude(Opus 4.8),保留真正的秘密并丢弃良性标识符;LLM 确认的区域会被填充为红色。
ANTHROPIC_API_KEY=… screenscrub clean ~/Screenshots --out ~/clean --llm --yes
这有意打破了本地和静默的默认行为:它将候选字符串(检测到的秘密)发送到 Anthropic API,因此仅限 opt-in,会打印警告,并且需要 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 crypt 哈希、PuTTY 和 OpenSSH 私钥、SendGrid/npm 令牌。password、token=、secret、api_key、……)——即使不匹配任何模式且熵较低,也会标记相邻的值。BEGIN/END 标记,并慷慨地隐去整个包围区域,而不是信任逐词匹配。每个命中都映射回组成它的令牌的像素框(两种行重构方法——间隔和紧凑——恢复 OCR 在空白处分割的秘密),并在填充前对框进行扩展。
接收端是一种方法:Store(Secret) error。提供的适配器:
file://——age 加密的本地文件(默认)。 无外部依赖;加密到 age scrypt 密码短语(或 age 接收者——见下方基于硬件的加密)的 JSON 记录数组。op://——1Password CLI。 运行 op item create,值通过 stdin 传递(绝不在 argv 中)。keepassxc://./vault.kdbx——KeePassXC。 流行的跨平台本地 KeePass 管理器。运行 keepassxc-cli add;数据库密码和秘密都通过 stdin 传递(绝不在 argv 中)。数据库密码是解析出的密码短语(--config/SCREENSCRUB_PASSPHRASE)。keychain://screenscrub——macOS Keychain。 运行 security add-generic-password。注意: security 工具仅接受秘密作为命令行参数,因此在运行时对于同一用户的 ps 短暂可见(macOS 对其他用户隐藏 argv)。若要求更严格的卫生习惯,请优先选择 1Password、KeePassXC 或下方的 Secure Enclave 接收者。vault://——HashiCorp Vault KV v2 ——已存在但推迟/不太常见;路径会哈希秘密,因此绝不在 URL 中出现。要添加你自己的接收端,实现 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 测试在缺少 tesseract 时跳过
主要保证由端到端测试涵盖:渲染一个包含 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 接口后面的自然更高精度后端——未来可能添加的候选。Vendored 凭据模式来自 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 | --llm 的 Anthropic API 密钥(或 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) |