Git hook audit .git/hooks/ persistence + injection audit (G1-G7).
Every developer host has dozens of git repos. .git/hooks/*
runs with the user's privileges on every routine git operation — and
a poisoned post-checkout runs silently on every
git pull. That's a textbook silent-persistence primitive.
The existing TrapDoor detector only matches specific campaign markers
in hooks; this auditor catches the general patterns.
Strictly local
The auditor never runs a hook, never alters the repo, never calls
out to the network. It walks the directory tree, finds every repo
(.git/ child), honors core.hooksPath if
overridden, parses each executable hook, and records:
- sha256, size, file mode, executable bit, first line
- silent-operation classification (post-checkout / post-merge / post-rewrite / pre-push / post-commit fire on routine operations)
is_known_sample— bare git-init hooks (shebang + comments only, no body) are skipped- six suspicious-pattern flags (see layers below)
What's skipped
- Vendored directories:
node_modules,.venv,venv,build,dist,__pycache__,.tox,.cache,site-packages— they often ship leftover.git/from packages *.samplefiles — git ships these on init; not customer code- Bare git-sample hooks recognized as the literal init body
Safety caps: 1 MiB per hook, 64 hooks per repo, 5000 repos per root.
Detection layers (G1–G7)
| ID | Severity | What it catches | MITRE |
|---|---|---|---|
| G1 | critical | Pipe-to-shell download cradle: curl ... | sh. Essentially never legitimate in a hook. | T1059 |
| G2 | high | Network-fetch in a silent-operation hook (post-checkout / post-merge / post-rewrite / pre-push / post-commit). Fires on every routine git operation — any C2 callback becomes every-time-the-dev-touches-git. | T1546 |
| G3 | medium | Network-fetch in a non-silent hook. Rarely needed; manual review. | T1546 |
| G4 | high | Eval of attacker-controllable input (eval "$VAR" / eval $1). In commit-msg the input is the commit message itself; in post-checkout it's the ref name. | T1059 |
| G5 | high | Self-modifying hook (writes to .git/config, .git/hooks/, .git/packed-refs). Hook persistence + self-replication primitive. | T1546 |
| G6 | medium | Long base64 or escaped-hex sequence in hook. Often hides a download cradle or shellcode. | T1027 |
| G7 | info | Silent-operation hook present (no other layer fired). Surface-area awareness so a tampered one stands out next time. | T1546 |
A clean is_known_sample hook emits NO findings. The
"stronger finding suppresses weaker" semantics apply: G1 suppresses
G2/G3; any specific finding suppresses the G7 info finding.
CLI
$ digger git audit-hooks --case-dir /tmp/case [--roots ~/src,~/work]
[git] executable hooks audited: 47
[git] pipe-to-shell: 0
[git] network-fetch: 2
[git] eval input: 0
[git] self-modifying: 0
[git] encoded payload: 1
[git] silent-op hooks: 18
[git] artifacts emitted: 47