Strip invisible tracking characters, URL tracking parameters, image metadata (EXIF/GPS/ICC), and PII from text and images — locally, before you paste or upload them anywhere.
No network calls. Ever. Single static-ish binary, no runtime deps.
$ ls -lh target/release/scrub
-rwxr-xr-x 1 root root 1.9M target/release/scrub
Copy-pasted text and screenshots carry more hidden baggage than people realize:
- Invisible Unicode: zero-width spaces/joiners (
\u200B,\u200D), directional-override controls, and variation-selector steganography are used to watermark and trace copied text. - Disguised whitespace: non-breaking spaces and other lookalike space characters are sometimes swapped in as a fingerprint.
- Homoglyphs: Cyrillic/Greek letters that render identically to Latin ones can hide inside otherwise-normal-looking text.
- URL telemetry:
utm_*,fbclid,gclid,si,msclkid, etc. leak cross-platform identity every time you share a link. - Image metadata: EXIF (camera make/model), GPS coordinates, and embedded ICC color profiles can fingerprint the exact device that took a screenshot or photo.
- Leaked secrets: emails, live API keys, JWTs, and internal IPs get pasted into prompts and public repos constantly, by accident.
scrub handles all of it locally in one pass, in milliseconds.
Requires a Rust toolchain (1.74+).
# Core build: text + URL + PII + image scrubbing, no clipboard, no GUI deps
cargo build --release
# Optional: also enable --clip (reads/writes the system clipboard)
cargo build --release --features clipThe binary lands at target/release/scrub.
# Clean text straight from the system clipboard (requires --features clip build)
scrub --clip
# Pipe text or prompt data through before it hits an LLM tool or a paste
cat sensitive_prompt.txt | scrub | llm-cli
# Also redact emails, API keys, JWTs, and internal IPs
cat sensitive_prompt.txt | scrub --scrub-pii
# Scrub an image before uploading it to a public repo
scrub --image screenshot.png --out clean_screenshot.pngEvery run prints a one-line-per-finding summary to stderr (so it never pollutes stdout when you're piping into another tool):
$ echo 'check https://x.com?utm_source=x fbclid=y [email protected]' | scrub --scrub-pii
check https://x.com [email protected]
scrub: stripped 1 URL tracking parameter(s)
scrub: redacted 1 email match(es)
Pass --quiet to suppress that summary entirely.
| Flag | Effect |
|---|---|
--clip |
Read from and write back to the system clipboard instead of stdin/stdout |
--scrub-pii |
Redact emails, API keys/tokens, JWTs, and internal IPv4 addresses |
--keep-homoglyphs |
Don't fold lookalike Cyrillic/Greek letters back to ASCII |
--keep-url-trackers |
Don't touch query parameters on URLs |
--image <PATH> |
Scrub a PNG/JPEG's metadata instead of running text mode |
--out <PATH> |
Output path (required with --image; optional in text mode) |
--quiet |
Suppress the stderr summary |
No image is decoded or re-encoded — scrub parses the container format
directly and drops only the metadata-bearing segments, so pixel data is
untouched byte-for-byte and the operation is sub-millisecond even on
real photos.
- PNG:
tEXt,zTXt,iTXt(free-text metadata),eXIf(embedded EXIF),tIME(timestamp),iCCP(embedded color profile). - JPEG:
APP1(EXIF/GPS/XMP),APP2(usually an embedded ICC profile),APP13(Photoshop metadata block),COM(comments).
Conservative, local regex matching only — no ML model, no network call:
- Email addresses
- JWTs (
eyJ...........) - API keys/tokens for common formats: OpenAI (
sk-), Anthropic (sk-ant-), AWS (AKIA/ASIA), GitHub (ghp_/gho_/github_pat_), Slack (xox*), Google (AIza), GitLab (glpat-), npm (npm_), SendGrid, Stripe. - Internal/private IPv4 addresses (RFC1918, loopback, link-local)
- Rust, small dependency tree. Core build only needs
regexandclap. Clipboard support (arboard) is feature-gated behind--features clipso the default binary never needs a display server or GUI libs and always builds cleanly in headless CI. - No image crate. Metadata stripping is a direct PNG chunk / JPEG marker-segment parser, not a decode-modify-re-encode round trip. This keeps output pixel-identical to the source and keeps the binary tiny.
- URL cleaning is allowlist-aware, not blind. It only strips a curated
list of known tracking parameters and special-cases YouTube's
t=(timestamp) so functional query parameters are never touched.
cargo test --release20 unit tests cover invisible-character stripping, homoglyph folding, URL tracking-parameter removal (including the YouTube-timestamp edge case), PII redaction for every supported pattern, and PNG/JPEG metadata stripping verified against real files generated with Pillow/piexif.