Everything in this repo ships to the world twice: it is a public GitHub repo, AND the whole repo is installed onto users' machines as a plugin. A leaked secret or internal detail is permanent — git history survives force-pushes in forks, caches, and mirrors. Prevention is the only cheap moment to act.
What counts as sensitive here
- Credentials of any kind: API keys, tokens, private keys, passwords — including "test" or "expired" ones (they reveal naming schemes and invite confusion).
- Personal / employee data: email addresses, personal home paths like
/Users/<name>/...(they leak usernames), real names of non-maintainers. - Internal workspace links: Notion pages, Slack archives, Discord channels, Linear issues, Google Docs, Datadog dashboards. Public GitHub links and
app.heptabase.complaceholder patterns are fine. - Real identifiers: actual card/workspace/chat UUIDs from anyone's Heptabase data. Docs must use placeholders like
<cardId>,<workspaceId>. - Infrastructure details: non-loopback IPs, internal hostnames, unreleased product details.
Workflow
- Before every commit, scan the staged changes (this is also what the pre-commit hook runs):
node .claude/skills/public-repo-guard/scripts/scan-sensitive.mjs --staged - Judge each finding — the scanner errs toward flagging; a finding is a question, not a verdict:
- Real sensitive data → remove it, replace with a placeholder (
<your-token>,<cardId>), or move it to private notes. Never "temporarily" commit it. - False positive (e.g. a deliberate documentation example) → append a
public-okmarker comment to that line and say why in the PR. The scanner skips marked lines.
- Real sensitive data → remove it, replace with a placeholder (
- Before a release or when auditing, scan every tracked + untracked file:
node .claude/skills/public-repo-guard/scripts/scan-sensitive.mjs --all - The scanner is a net, not a guarantee. It only knows patterns. Also apply judgment to things regex cannot see: screenshots, real user data or support-conversation excerpts, names/handles of people who did not consent, anything you would not put in a tweet. When reviewing, read the actual diff (
git diff --cached) — not just the scan output.
Scanner reference
scripts/scan-sensitive.mjs [--staged | --all | <paths...>]
--staged(default): scans only lines being ADDED by the staged diff, plus staged filenames (catches.env,.envrc,*.pem,id_rsa, etc. even when their content evades patterns).--all: scans all tracked and untracked-but-not-ignored files. Use before releases.<paths...>: scans the given files fully. Use for reviewing a single doc.- Exit code 0 = clean, 1 = findings to judge, 2 = usage error. Credential-like matches are printed masked so secrets don't end up in terminal logs.
- Built-in allowances:
example.com/noreply@/users.noreply.github.comemails,git@…SSH clone URLs, loopback/any IPs (127.0.0.1,0.0.0.0, …), synthetic repeated-digit UUID placeholders (11111111-1111-4111-…), and lines carrying thepublic-okmarker.
Hard enforcement: git pre-commit hook
Install once per clone (hooks are local, never committed):
node .claude/skills/public-repo-guard/scripts/install-git-hook.mjs
Every git commit (including via gt/Graphite) then runs the staged scan and blocks on findings. Bypassing with git commit --no-verify should be a deliberate, explained exception — if you bypass, say so in the PR description so a reviewer double-checks.
If something sensitive already got committed
- Treat any leaked credential as compromised the moment it is pushed: rotate/revoke it first — history cleanup is NOT mitigation.
- MUST tell the maintainer IMMEDIATELY.
- Removing it from history (BFG /
git filter-repo+ force-push) breaks clones and installed plugin caches — coordinate before attempting, and remember public forks/mirrors may retain the data anyway.