Skip to content
Back to skills

Pre Commit Audit

ASecurity

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'.

  • 144 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentspythongobashawsgitapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 23, 2026

npx -y skills add flonat/flonat-research --skill pre-commit-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pre Commit Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pre Commit Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/flonat-pre-commit-audit/badge)](https://www.skillsdirectory.com/skills/flonat-pre-commit-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

1. **Size pass blocks anything >10MB unless gitignored OR user explicitly approves.** Threshold matches `block-large-files.sh` hook for consistency.
2. **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.
3. **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.
4. **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.
5. **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

6. Output a single consolidated table (file × pass × verdict) — not four separate reports.
7. Severity tiers: BLOCK (must fix), WARN (proceed with confirm), OK.
8. 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

```bash
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`](../_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:

```bash
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):

```bash
uv run --no-sync python "<skill-dir>/scripts/check_invisible_unicode.py" <path> --diff    # preview
uv run --no-sync python "<skill-dir>/scripts/check_invisible_unicode.py" <path> --apply   # write
```

`--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.

Files in this skill

  • SKILL.md11 KB
  • scripts/check_invisible_unicode.py21.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.