| name | pre-commit-audit |
| description | Deliver a fast pre-commit safety scan: file size, anonymity (author / affiliation strings in tex/bib), hardcoded secrets, and invisible-Unicode carriers. Use when the user requests a fast pre-commit safety scan: file size, anonymity (author / affiliation strings in tex/bib), hardcoded secrets, and invisible-Unicode carriers. Triggers: 'audit before commit', 'check before push', 'pre-commit scan', 'safety check'. |
| allowed-tools | Read, Bash(git*), Bash(grep*), Bash(find*), Bash(stat*), Bash(numfmt*), Bash(wc*), Bash(head*), Bash(uv*), AskUserQuestion |
| argument-hint | [--staged | --unstaged | --all] |
Pre-Commit Audit — File Size, Anonymity, Secrets
Three-pass safety scan before commit. Blocks pain-points: oversized commits (1.3GB parquet incident), CCS-style anonymity breaches, leaked credentials. Each pass is a hard gate; user OKs proceed.
Hard Rules
Existential — block proceed
- Size pass blocks anything >10MB unless gitignored OR user explicitly approves. Threshold matches
block-large-files.sh hook for consistency.
- Anonymity pass runs only on paper-relevant paths:
paper-*/, *.tex, *.bib, *.md inside paper directories. Scope is data-driven — we don't false-flag README authorship.
- Secrets pass uses an entropy heuristic + known-prefix list. No regex-only matching that misses high-entropy keys. Block on confirmed secret; warn on suspicious-but-uncertain.
- All four passes run on the same file set (default: staged for commit). Don't mix staged-only size check with all-files anonymity check.
- The Unicode pass is the one pass that is always in scope, including infra-only commits. It is identity-blind — it reads codepoints, never names — so it cannot false-flag authorship the way the anonymity pass can.
Format — catch in review
- Output a single consolidated table (file × pass × verdict) — not four separate reports.
- Severity tiers: BLOCK (must fix), WARN (proceed with confirm), OK.
- Always show the file path and line number when flagging — make it copy-paste-fixable.
When to Use
- Before any
git commit on a paper / project repo
- Pre-submission checklist (research projects)
- Before pushing to anonymous-artifact repo (the highest-risk anonymity surface)
- After a sub-agent has written files (verify before committing their work)
- Wired as a
session-close Phase 4 sub-step (alternate path to running the hook)
When NOT to Use
- Before commits on infrastructure-only changes (skills, hooks, rules) — anonymity scan would false-flag README author lines. Exception: Phase 4 (Unicode) stays valid there and can be run alone:
pre-commit-audit --unicode-only
- Auto-generated commits (CI, dependabot) — they don't need the scan
- When the change is purely metadata (
.gitignore, LICENSE, etc.)
Modes
| Invocation | Scope |
|---|
pre-commit-audit | Staged files (default) |
pre-commit-audit --staged | Same as default |
pre-commit-audit --unstaged | Modified-but-not-staged files |
pre-commit-audit --all | Working tree + staged |
pre-commit-audit --unicode-only | Phase 4 only — safe on infra commits |
Architecture
Phase 1 (size) → find files >10MB; check .gitignore status
Phase 2 (anonymity) → grep paper paths for author/affiliation/email
Phase 3 (secrets) → entropy + prefix heuristics on all staged files
Phase 4 (unicode) → invisible-carrier scan via check_invisible_unicode.py
Phase 5 (consolidate) → single table; user OK to proceed
Phase 1: Size
THRESHOLD_BYTES=$((10 * 1024 * 1024))
git diff --cached --name-only --diff-filter=ACM | while read f; do
if [ -f "$f" ]; then
size=$(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null || echo 0)
if [ "$size" -gt "$THRESHOLD_BYTES" ]; then
ignored=$(git check-ignore "$f" 2>/dev/null && echo "ignored" || echo "tracked")
echo "$f|$size|$ignored"
fi
fi
done
Verdict: BLOCK if any tracked file >10MB. WARN if gitignored but staged anyway.
Phase 2: Anonymity
Single source of truth: skills/_shared/double-blind-anonymity-checklist.md holds the authoritative pattern list (paper-side P1-P8, artifact-side A1-A6) plus the CCS 2026 #1328 incident report that motivates the checks. Both pre-commit-audit (this skill) and anonymous-artifact reference it; do not duplicate the patterns inline.
Scope for pre-commit-audit: only files staged for commit at paths matching paper-*/, **/*.tex, **/*.bib, **/*.cls, **/*.sty. The full checklist also covers structured-metadata files (pyproject.toml, package.json, Cargo.toml, CITATION.cff, etc.) — those are checked by anonymous-artifact since they only matter when pushing the artifact, not on every commit.
Apply checks P1-P8 from the shared checklist:
- P1 — title page authors blinded
- P2 — no
\thanks{} / \acknowledgements / \funded / \grant revealing identity
- P3 — first-person self-reference uses third person (no "our prior work")
- P4 — self-citation bib entries blinded if author overlap
- P5 — body text doesn't name authors of self-cited works
- P6 — no identifying URLs (personal sites, lab pages, repos with handles)
- P7 — PDF metadata sanitized (
pdfinfo paper.pdf)
- P8 — figures contain no identifying watermarks/captions
For each match: print path:line and the matched text (truncated). Verdict: BLOCK on any match in a path containing paper-*/. WARN on matches outside paper directories.
Exception: a frontmatter-style Made by the user in friends-repo / public-repo READMEs is allowed (intentional attribution).
Phase 3: Secrets
Patterns by category:
| Category | Detection |
|---|
| API keys with prefix | sk-, sk-proj-, sk-ant-, hf_, gh[a-zA-Z]_, xoxb-, AIza |
| AWS | AKIA[0-9A-Z]{16}, aws_secret_access_key |
| Generic high-entropy strings | length ≥32, base64-ish character set, no whitespace |
.env-style assignments | ^[A-Z_]+=[a-zA-Z0-9+/=_-]{20,} outside .env.example/.env.template |
Skip .env.template, .env.example, *.example, comment lines starting with #.
Verdict: BLOCK on any match in a non-template file.
Phase 4: Invisible Unicode
Catches characters that are invisible on screen but change what a machine reads — zero-width carriers, bidi overrides, tag characters, and space homoglyphs. These arrive by paste (Word, PDF, web page, LLM output) and fail silently: a U+200B inside a citekey makes BibTeX drop the entry with no error, a U+00A0 makes an exact-match string edit fail on a line that looks identical.
Deterministic — delegate to the script rather than grepping by hand:
git diff --cached --name-only --diff-filter=ACM \
| while read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done \
| xargs -r uv run --no-sync python "<skill-dir>/scripts/check_invisible_unicode.py" --format json
Map the script's severities onto this skill's tiers:
| Script severity | Meaning | Verdict |
|---|
critical | Zero-width / bidi / tag char, or any invisible inside a citekey, crossref, path, url, bib_entry_key, bib_doi | BLOCK |
warning | Space homoglyph or soft hyphen in prose — renders fine, breaks exact-match editing and grep | WARN |
rescued | Legitimate emoji ZWJ / VS16 sequence | not reported |
Report path:line:col, the U+XXXX label, and the containing construct when the JSON carries a context. The context is most of the signal: the same U+00A0 is a WARN in prose and a BLOCK inside \ref{}.
Fixes are mechanical, so offer them — but never apply silently (Anti-Patterns below):
uv run --no-sync python "<skill-dir>/scripts/check_invisible_unicode.py" <path> --diff
uv run --no-sync python "<skill-dir>/scripts/check_invisible_unicode.py" <path> --apply
--apply refuses Overleaf-canonical paths unless given --yes-live-surface, per reconcile-before-rewriting.md; if a flagged file is a live Overleaf surface, surface the finding and let the user fix it in the editor rather than passing that flag on their behalf.
Phase 5: Consolidate + ask
Single table:
File Size Anon Secrets Uni Verdict
────────────────────────────────────────────────────────────────────────────
paper-ccs/paper/main.tex 5KB OK OK OK OK
paper-ccs/paper/references.bib 12KB FAIL OK FAIL BLOCK (line 23: the user; line 8: U+200B in bib_entry_key)
data/raw/results.parquet 1.3GB N/A N/A N/A BLOCK (size; not gitignored)
scripts/sync-creds.sh 2KB OK FAIL OK BLOCK (line 15: hf_<...>)
notes/from-word.md 4KB OK OK WARN WARN (4x U+00A0)
If any BLOCK rows: ask the available structured-question mechanism:
- Fix the issues and rerun — recommended
- Override and proceed — risky, requires explicit confirmation
- Cancel commit — abort
If only WARN rows: ask whether to proceed.
If all OK: print "Pre-commit clean ✓" and exit silently.
Cross-References
| Skill / Hook / Rule | Relationship |
|---|
block-large-files.sh hook | Runtime enforcement of Phase 1 (this skill is the manual / verbose counterpart) |
scripts/check_invisible_unicode.py | Bundled deterministic engine behind Phase 4. Codepoint tables adapted from guillaumemeyer/watermarks-remover (MIT); severity tiers, LaTeX-context detection, and emoji rescue are local |
reconcile-before-rewriting.md rule | Governs Phase 4's --apply on live Overleaf surfaces |
anonymous-artifact | The anonymity-on-publish workflow; this skill is the pre-commit equivalent |
session-close Phase 4 | Can invoke this skill as part of pre-commit verification |
mark-unverified.md rule | Verbal counterpart for content claims; this skill handles file content |
Anti-Patterns
- Don't scan binary files for anonymity strings — false positives on PDFs, images.
- Don't rely solely on regex for secret detection — entropy heuristics catch keys with novel prefixes.
- Don't auto-fix issues — surface them for the user to handle. Auto-replacement of names risks breaking acknowledgements.
- Don't skip the .gitignore check — large gitignored files staged anyway are a signal of
git add -A mistakes.
- Don't require this skill on infra-only commits. Add a path filter or let the user opt out for those — but
--unicode-only remains safe and useful there.
- Don't run Phase 4's
--apply as part of the audit. The audit reports; the user decides. Auto-rewriting bytes in a file the user is mid-edit on is the exact failure reconcile-before-rewriting.md exists to prevent.
- Don't flag visible non-ASCII. Accented names (
López-Ibáñez), em dashes, and → are legitimate and the script deliberately ignores them; a pass that flags them will be muted within a week.