scrubpii
v0.1.1
Published
Find and redact PII & secrets — emails, Luhn-valid credit cards, JWTs, API keys, IBANs, SSNs, IPs — from JSON, logs and text. 100% local, no upload. Deterministic detection, referential-integrity pseudonyms, JSON-path & key-aware redaction. CLI + library,
Maintainers
Readme
🧽 scrubpii
Make any payload, log, or fixture safe to share — strip PII & secrets locally, in one pipe.
🌐 Try the browser playground → · paste a payload, get a safe-to-share copy. Nothing is uploaded — it all runs client-side.
You need to attach a production API response to a GitHub issue. Or commit a realistic test fixture. Or drop a log into Slack so a teammate can repro. But it's full of emails, card numbers, JWTs, API keys, IPs, SSNs — and the one tool everyone reaches for, an online redactor, asks you to upload the very data you're trying to protect.
scrubpii finds and redacts PII & secrets from JSON, logs and text — 100% locally, deterministically, in one command. Nothing is uploaded. Nothing leaves your machine.
cat payload.json | npx scrubpii redact- "email": "[email protected]",
+ "email": "[email protected]",
- "card_number": "4242 4242 4242 4242",
+ "card_number": "redacted_1",
- "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...",
+ "authorization": "redacted_2",Why scrubpii (and not a quick sed or an LLM)?
- 🔒 Local & deterministic. No server, no API key, no telemetry. Same input → same output, every time. Safe for secrets because it never sees the network.
- 🎯 Precision over noise. Credit cards are confirmed with the Luhn checksum, IBANs with mod-97, JWTs by decoding the header — so a random 16-digit order ID isn't flagged as a card. A redactor that cries wolf gets turned off.
- 🔗 Referential integrity.
[email protected]becomes[email protected]everywhere it appears — so the redacted document still shows that two log lines are the same user.sedand online tools destroy that. - 🧩 JSON-aware. Walks the document, redacts values whose key is sensitive
(
password,authorization,api_key— even with no detectable pattern) and scrubs PII hiding inside free-text fields. Output is still valid JSON, same shape. - 🪶 Zero-dependency core. The library imports nothing at runtime and runs in the
browser, Node, Deno and Bun. The CLI adds only
cac+picocolors.
Why not just ask an LLM? You can't paste a 50 MB log into a chat, you can't put a chatbot in CI, and "redact the secrets" from a model is non-deterministic — it will miss some and hallucinate others. scrubpii is a checksum, not a guess.
Install
# zero-install, run it now
npx scrubpii scan payload.json
# or add it
npm install -g scrubpii # global CLI
npm install -D scrubpii # CI / pre-commit dependency
npm install scrubpii # libraryNode ≥ 18. Ships ESM + CJS + TypeScript types.
Quick start
# 1. See what's in there (read-only, nothing changes)
scrubpii scan payload.json
# 2. Get a safe copy on stdout
cat payload.json | scrubpii redact > safe.json
# 3. Use it as a CI / pre-commit guard — exit 1 if anything is found
scrubpii scan ./src --max-findings 0scan — find, don't touch
$ scrubpii scan examples/app.log
examples/app.log (text) 10 findings
1:42 Email address a***@***.com
2:41 JWT eyJ…90
3:46 Credit card (Luhn-valid) **** **** **** 4242
4:45 URL credentials svc…ss
6:36 AWS access key AKI…LE
6:62 GitHub token ghp…AB
Summary 4 secrets · 4 PII · 2 network across 1 filePreviews are always masked — scrubpii never prints the full secret, not even in its own reports.
redact — produce a safe copy
scrubpii redact users.json --out clean.json # pseudonyms (default)
scrubpii redact users.json --strategy hash # stable [EMAIL:32oeg5] tokens
scrubpii redact users.json --strategy label # [EMAIL], [REDACTED]
scrubpii redact ./logs --in-place # rewrite a whole folder
echo "ping [email protected]" | scrubpii redact # pipe anything| Strategy | [email protected] becomes | Keeps links? | Best for |
| ------------ | ------------------- | :----------: | -------- |
| pseudonym | [email protected] | ✅ | shareable, realistic fixtures (default) |
| hash | [EMAIL:32oeg5] | ✅ | stable IDs across files, no counter state |
| label | [EMAIL] | ❌ | quickest "just hide it" |
| partial | a***@***.com | ❌ | keep a human hint |
| remove | (deleted) | ❌ | strip entirely |
What it detects
Secrets — private keys, AWS access keys, GitHub / Slack / Stripe / OpenAI /
Google / SendGrid keys, JWTs, user:pass@ URL credentials.
PII — emails, Luhn-valid credit cards, mod-97 IBANs, US SSNs, phone numbers.
Network — IPv4, IPv6, MAC addresses.
JSON keys — any value under password, secret, token, authorization,
api_key, cvv, pin, … (configurable), even when the value has no pattern.
Use it in your workflow
1. Sanitize before you paste. Bug report, Slack, screen-share — one pipe makes a prod payload safe to share, with relationships intact so it's still reproducible:
kubectl logs my-pod | scrubpii redact | pbcopy2. Commit realistic fixtures from real data. Redact a prod export into
test/fixtures with consistent pseudonyms, so foreign keys and "same user across
rows" still hold — something hand-redaction always breaks:
scrubpii redact prod-export.json --strategy hash --out test/fixtures/users.json3. Guard your repo / CI. Fail the build (or a pre-commit hook) the moment a real secret or customer record is about to be committed:
# .github/workflows/ci.yml
- run: npx scrubpii scan . --max-findings 0 --md scrubpii-report.md# .husky/pre-commit
git diff --cached --name-only | xargs scrubpii scan --max-findings 0Library API
import { redact, detect, Vault } from "scrubpii";
// Auto-detects JSON vs text:
const { output, findings, format } = redact(input, { strategy: "pseudonym" });
// Read-only detection with line/column (great for editors & CI):
for (const f of detect(logText)) {
console.log(`${f.line}:${f.column} ${f.label} ${f.preview}`);
}
// Share one Vault to keep pseudonyms consistent across many documents:
const vault = new Vault("pseudonym");
const a = redact(file1, {}, vault);
const b = redact(file2, {}, vault); // same person → same alias in bothAlso exported: redactText, redactJson, redactJsonValue, looksLikeJson,
DETECTORS, luhnValid / ibanValid / jwtValid / ssnValid, and all types.
Configuration
scrubpii init writes scrubpii.config.json:
{
"strategy": "pseudonym",
"enable": [], // [] = all types; or e.g. ["email", "credit-card"]
"disable": ["phone"], // turn specific detectors off
"redactKeys": ["password", "token", "authorization", "..."],
"salt": "", // mixed into the hash strategy
"maxFindings": 0 // CI gate threshold
}CLI flags override the file: --only, --except, --strategy, --salt,
--max-findings, --format json|text|auto, --config <file>.
Roadmap
- 🤖 Optional
--ailayer (bring-your-own key). A pluggable NER pass to catch free-text names/addresses that rules can't — strictly opt-in; the core stays 100% offline and deterministic. - 🔁 Reversible pseudonyms. Write the mapping to a local keyfile so a redacted fixture can be re-hydrated for debugging.
- ➕ More secret providers, custom regex detectors, and per-path ignore rules.
- 🌊 Streaming mode for multi-GB logs.
- ✅ 🌐 A browser playground (paste a payload, see redactions — nothing uploaded). Live here.
Issues and PRs welcome — especially new detectors with test vectors.
💖 Sponsor
scrubpii is free and MIT-licensed, built and maintained in spare time. If it saved you from leaking a customer record (or an afternoon of manual redaction), please consider supporting it:
- ⭐ Star this repo — the simplest free way to help others find it.
- 🍋 Sponsor via Lemon Squeezy — one-time or recurring.
License
MIT © scrubpii contributors
