Guides / Scrub a repo for personal data before you publish

Scrubbing a repo for personal data before you publish

2026-10-11 · 745 words · Pack: Release Scrub

The folder you are about to open-source has been private for a year. In that year a fixture was copied from production, a sender address went into the mailer, a client's name landed in an architecture note, and somebody put a real key into .env.example "temporarily". None of it is in the files you re-read before publishing, because you re-read the README and the main module, and personal data lives everywhere else.

A scan is cheap and a leak is not. This guide builds the scan, explains the detectors that keep false positives low enough that you will actually run it, and sets the bar for "clean".

It matters twice as much when an agent produced the code. Agents copy examples faithfully: asked to write a fixture, they will lift a real record from the nearest database dump; asked to document an integration, they will paste the real endpoint, the real account id and the real contact. The output looks polished, which is exactly why nobody re-reads it before the push. Treat anything generated from real data as suspect until the scanner says otherwise.

What to detect

Six categories cover most of what leaks from a code repository:

  • Email addresses — skipping reserved example domains so documentation samples do not trigger.
  • Phone numbers — international +CC … forms and common national formats, 8–15 digits.
  • IBANs — the one place a checksum is available, so use it.
  • API keys and tokens — the published shapes: sk_live_, sk-, AKIA, ghp_, xox…, AIza, raw bearer tokens, whsec_.
  • Private key blocks — -----BEGIN … PRIVATE KEY-----.
  • Names — people, companies, internal project names, from a config file you keep outside the folder being published.

Keeping false positives down

A scanner that cries wolf gets disabled. Three decisions make the difference.

First, validate IBANs with the ISO 13616 mod-97 check instead of trusting the pattern. A 20-character alphanumeric string that happens to start with two letters and two digits is common in test data; one that passes mod-97 is almost certainly real:

export function ibanValid(raw: string): boolean {
  const s = raw.replace(/\s+/g, "").toUpperCase();
  if (s.length < 15 || s.length > 34) return false;
  const rearranged = s.slice(4) + s.slice(0, 4);
  let remainder = 0;
  for (const ch of rearranged) {
    const v = ch >= "A" && ch <= "Z" ? String(ch.charCodeAt(0) - 55) : ch;
    for (const d of v) remainder = (remainder * 10 + Number(d)) % 97;
  }
  return remainder === 1;
}

Second, reject phone candidates that are obviously something else — part of a semver string, a date, or sitting next to a long hex hash:

function looksLikeHashOrVersion(line: string, match: string): boolean {
  if (/\b\d+\.\d+\.\d+\b/.test(match)) return true;
  if (/[0-9a-f]{20,}/i.test(line)) return true;
  if (/\d{4}-\d{2}-\d{2}/.test(match)) return true;
  return false;
}

Third, skip what cannot contain prose leaks: binaries (by extension and by a null-byte sniff of the first kilobyte), node_modules, .git, build output, lockfiles, and anything in the config's ignore list.

Make the report safe to share

Findings are going to be pasted into an issue or a chat, so the report must not itself be a leak. Every match is redacted to its first three and last two characters plus the length, and the context line is shown with the match replaced:

src/mailer.ts:14  [email]  fin…om (24)
    const FROM = "fin…om (24)";
test/fixtures/customer.json:6  [iban]  LT1…00 (20)
    "iban": "LT1…00 (20)",
.env.example:2  [apiKey]  sk_…Q7 (41)
    STRIPE_SECRET=sk_…Q7 (41)

That is enough to find and fix each line, and not enough to reconstruct anything.

The names list is the config, and the config stays home

A scrub.config.json holds three things: names to report wherever they appear (case-insensitive, word-bounded), ignore path substrings, and allow — literal values or /regex/ that are safe by design, such as a public support address or Stripe's sk_test_ prefix.

{
  "names": ["Jane Example", "Acme Internal"],
  "ignore": ["node_modules", "dist/", "CHANGELOG.md"],
  "allow": ["noreply@example.com", "/^\\+1 555 01\\d\\d$/", "sk_test_"]
}

Keep this file outside the folder you publish. It is, literally, a list of the things you are hiding.

Triage, then re-run

Run the scan:

bun .claude/skills/release-scrub/scripts/scrub.ts ./invoice-tool --config ~/private/scrub.config.json

It exits 0 when clean and 1 when there are findings, so it drops into a pre-publish job unchanged. For every finding, decide one of three things:

  • Redact — real personal data or a secret. Replace with a placeholder (user@example.com, +1 555 0100, sk_live_REPLACE_ME) or delete the line. If it was a live key, rotate it; redacting the file does not un-leak the history.
  • Allow — safe by design. Add the literal to allow with a comment saying why. Never allow a whole detector to silence it.
  • False positive — the pattern matched something harmless. Note it; if it recurs, add a targeted regex.

A folder is clean when a re-run after redaction exits 0. Not when you believe you fixed everything — when the scanner agrees.

What the scanner does not see

It reads file contents, not file or directory names — a folder called clients/northwind/ is yours to catch. It skips binaries, so screenshots and PDFs get eyeballed by a human. And it does not scan git history: a clean working tree on top of a dirty history still leaks the moment someone runs git log -p. Squash, or run a history filter, before the first public push.

Install

The Release Scrub pack is the skill with the triage workflow, scrub.ts, and the example config, tested against a fixture tree containing real-shaped emails, phones, a valid and an invalid IBAN, live and test keys, binaries and an ignored directory.

curl -fsSL https://hookcrate.com/install | bash   # Windows: irm https://hookcrate.com/install.ps1 | iex
hookcrate login hc_YOUR_KEY
hookcrate install release-scrub

$9/month or $99 lifetime; one license covers every pack in the catalog. (bunx hookcrate is coming soon on npm.)

The pack

Release Scrub Skill
Scans a folder for emails, phone numbers, IBANs, API keys and a configurable names list before anything is published.
View pack

$9 / month or $99 lifetime — one license covers every pack. Get access.