04 skill sanitizer
Public AI skills: small, useful agent workflows you can try today.
npx -y skills add animaresearch/skills --skill 04-skill-sanitizerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 10 stars10 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Pre-share leak scanner for Claude Code skills (or any folder). Scans BEFORE you publish for secrets (API keys, tokens, private keys, .env values, high-entropy strings), PII (emails/phones), local machine paths (C:\Users\<name>, /home/<name>, /Users/<name>, UNC, internal hostnames), and client/proper-noun names from a LOCAL private dictionary; also flags service-specific code (jira/github/gitlab/slack/salesforce/notion/linear/ stripe/aws/google) that should be split into a dedicated service plugin. Use when the user says "sanitize this skill", "before sharing a skill", "check for leaks", "is this skill clean", "is this safe to commit/publish/ open-source", "scrub secrets", "redact before commit", or "pre-push leak check". Cross-platform, stdlib-only Python, fail-closed.
SKILL.md
9.3 KB, as published. Nobody here has run it
skill-sanitizer
Scan a skill directory for things that must not leak publicly, BEFORE you share
or git push it. Reports BLOCK (hard-stop: real secrets, private keys, client
names, your own identity) and WARN (review: PII, local paths, internal hosts,
high-entropy strings, embedded service code).
When to use
- Before publishing / open-sourcing / zipping a skill to share.
- As a pre-push or CI gate over a skill folder.
- Auditing a folder someone handed you.
When NOT to use
- Not a runtime secret-rotation guard.
- Not a full-repo replacement for git-secrets/trufflehog (this is skill-folder scoped).
- It is report-only: it never modifies your files.
How to run
python scanner.py <skill_dir>
Common variants:
python scanner.py <skill_dir> --format json # pretty JSON to stdout
python scanner.py <skill_dir> --config .sanitizer.local.json
python scanner.py <skill_dir> --warn-ok # WARN-only => exit 0
python scanner.py <skill_dir> --no-entropy # disable the entropy backstop
The scanner prints a human report to stderr and, on the last stdout line, exactly:
RESULT_JSON={"version":"1.0","scanned_root":"...","findings":[{"file","line","category","severity","snippet","suggestion"}],"counts":{"block","warn","files_scanned"},"service_plugins_suggested":[...],"suppression":{"suppressed_files","ignore_patterns","dangerous_ignore_globs","ignore_file_suppressed_files","ignore_line_suppressed_lines","ignore_line_suppressed_findings"},"scan_errors":[...],"blocked":true|false,"exit_code":0|1|2|3}
snippet is always REDACTED (first 4 + last 2 chars) — the full secret is never
emitted to the report.
Exit codes
0clean (no BLOCK, no WARN) — safe to share. Also0for WARN-only with--warn-ok.1at least one BLOCK finding (secret / private key / client name / your own identity) — HARD FAIL pre-push.2no BLOCK but >=1 WARN (PII / path / host / entropy / plugin-split) — review consciously.3scan-integrity / usage error: bad path, malformed config, a path that could not be scanned (unreadable, permission, Windows MAX_PATH), OR a dangerous wholesale.sanitizerignoreglob (*,*.*,*.py,**, …) that could hide everything. Fail-closed — a wholesale ignore glob FAILS the gate by default so a naiveif exit==0: passCI check cannot be silently bypassed. Explicit named-file excludes (e.g. a skill ignoring its ownSKILL.md) stay0. An incomplete or integrity-compromised scan is never reported as clean.
Precedence when several apply: 1 (BLOCK) > 3 (scan-integrity) > 2 (WARN) > 0.
Fail-closed: any unreadable/binary file is byte-scanned for known-prefix BLOCK
patterns; UTF-16 (BOM or NUL-interleaved) files are decoded properly and
NUL-stripped before regex so they cannot bypass detection. The tool never
crashes and never silently passes.
How to read findings
The agent should:
- Locate the target skill dir.
- Load the local config if present (
.sanitizer.local.json). - Run the scanner.
- Present BLOCK findings FIRST (with redacted previews), then WARN.
- Propose the concrete fix printed with each finding.
Severities:
- BLOCK — known-prefix provider keys (
sk-,ghp_,AKIA…, etc.), private keys, secret-named env assignments with real values, connection strings with passwords, client/proper-noun dictionary terms, and your OWN auto-seeded identity (username/email/hostname/git name). Must be removed before sharing. - WARN — emails, phones, local machine paths, internal hosts/IPs,
high-entropy unknown strings, and
plugin-split:<service>advice.
Remediation guidance
- Secrets → remove and rotate the credential; reference it via an env var
(
os.environ[...]) or a.env.exampleplaceholder. Never commit the real value. - Local paths → replace
C:\Users\<you>//home/<you>with~,$HOME,%USERPROFILE%, or a<USER>/<project-root>token. - PII → replace personal emails with a reserved
example.comaddress; drop phone numbers, or allowlist your own contact in the local config. - Client / proper-noun names → genericize the name, or (if intentional and public) add it to your local allowlist.
- Service-specific code (
plugin-split) → the skill embeds code for a specific service (e.g. Jira). Extract that code into a dedicated<service>service plugin and depend on it, so this generic skill stays portable. Example: movejira/*logic into anatlassian-jiraplugin.
External local config (private dictionary — never committed)
The shipped skill ships ONLY generic patterns. Anything private (client names, project codenames, your own contact details) lives in an EXTERNAL, gitignored file that you keep locally:
.sanitizer.local.json # in the scanned dir, or any parent, or $HOME
The scanner walks up from the target dir to the filesystem root, then $HOME,
and loads the first .sanitizer.local.json it finds (cwd wins). The repo ships
ONLY .sanitizer.example.json as a template — the real local file must NEVER be
committed. Add this line to your .gitignore:
.sanitizer.local.json
Config shape (all keys optional, plain stdlib JSON):
{
"block_terms": ["Globex", "ProjectZephyr"],
"warn_terms": ["northwind"],
"client_names": ["AcmeCorp"],
"project_codenames": ["Bluebird"],
"own_username": "youruser",
"own_emails": ["[email protected]"],
"allowlist": { "emails": ["[email protected]"] },
"entropy": { "enabled": true, "min_len": 20, "base64_bits": 4.0, "hex_bits": 3.0 }
}
block_terms / client_names / project_codenames / own_username →
word-boundary, case-insensitive BLOCK. warn_terms → WARN.
Suppression channels (READ THIS BEFORE TRUSTING A CLEAN VERDICT)
The scanner has THREE ways findings can be hidden. A "clean" result is only
trustworthy once you have reviewed what, if anything, was suppressed. The
scanner ALWAYS reports suppression in both the human report (a SUPPRESSION
section) and RESULT_JSON (a "suppression" object), so the consuming agent
MUST read and relay it.
.sanitizerignore(gitignore-style globs at the scan root) — drops whole files before scanning. Counted assuppressed_files; the activeignore_patternsare echoed back. A dangerous wholesale glob (*,*.*,*.py,**, …) that could mask code prints a loudWARNINGAND forces a non-zero exit3(scan-integrity failure) — a.sanitizerignoreof*will never yield a silent green "Safe to share", and aif exit==0: passCI gate cannot be bypassed by it.# sanitizer: ignore-file— an ANCHORED standalone comment in the first ~10 lines of a file (the comment's content must START with the token; a bare substring buried in code/data does NOT count). Skips that whole file. Counted asignore_file_suppressed_files.# sanitizer: ignore-line— appended to a single line; drops all findings on that line. Counted asignore_line_suppressed_linesplusignore_line_suppressed_findings(how many findings were actually hidden) so a CI/agent can tell "genuinely clean" from "a directive hid something".
The consuming agent should:
- Read
RESULT_JSON.suppression(andscan_errors) BEFORE reporting "clean". - Treat any
dangerous_ignore_globs, nonzeroignore_line_suppressed_findings, or nonemptyscan_errorsas a reason to NOT report "safe to share" until the hidden content / unscanned paths have been reviewed. - Echo the suppression counts to the user alongside the verdict.
Unscanned paths / scan errors
If a path cannot be descended or read (Windows MAX_PATH, permissions), the
scanner does NOT report clean: it records the path under scan_errors, prints a
SCAN ERRORS section, and exits 3 (scan error) so the incompleteness is
surfaced. On Windows it retries with the \\?\ extended-length prefix first.
Self-clean guarantee (auto-seed + dogfood)
Even with NO config, the scanner derives your identity at runtime (in-memory
only, never written to disk) from $USERNAME/$USER, git config user.name/ user.email, and the hostname, and flags any of those as BLOCK — so your own
name/email/machine path is ALWAYS caught out of the box. The shipped skill
contains only generic regexes and reserved-example fixtures; running the
scanner on its own skill folder produces zero BLOCK findings (its own first
test / dogfood gate).