| name | security-precommit-check |
| description | Pre-commit security scanner for git repos — blocks API keys, proxy credentials, private keys, and project-specific secrets before they reach a remote. Provides a central rule definition + per-repo customization + one-line installer that wires up `.git/hooks/pre-commit`. Use when you want to prevent accidental credential / identity / brand leaks across multiple repos. Triggers on "set up pre-commit security check", "block API keys in commits", "git secret scanner", "prevent credential leaks", "protect identity isolation in public repos". |
Security Pre-Commit Check
Layered defense against accidental secret / identity leaks in git commits. Combines a central scanner + universal rule set (this skill) with per-repo custom rules and one-line installer that wires up .git/hooks/pre-commit.
Architecture
tools/security-precommit-check/
├── SKILL.md ← this file
├── scripts/
│ ├── scan.sh ← scanner (called by hook OR ad-hoc)
│ └── install-hook.sh ← one-line installer per repo
├── rules/
│ └── default.txt ← universal rules (API keys / proxies / paths)
└── templates/
└── security-precommit-rules.txt.example ← copy to each repo as starting point
In each repo where you install the hook:
<repo>/
├── .git/hooks/pre-commit ← auto-generated by install-hook.sh
└── .security-precommit-rules.txt ← per-repo rules (loaded in addition to default)
The hook delegates to scripts/scan.sh --staged, which reads BOTH default.txt (universal) AND the repo's .security-precommit-rules.txt (per-project).
Install (one line per repo)
SKILLS=~/Documents/Code/cross-border-ecommerce-skills
cd /path/to/your/repo
$SKILLS/tools/security-precommit-check/scripts/install-hook.sh
The installer:
- Writes
.git/hooks/pre-commit (idempotent — re-run to refresh)
- If
.security-precommit-rules.txt doesn't exist in your repo, copies the template
After install, every git commit runs the scanner against staged files. BLOCK matches refuse the commit; WARN matches print a warning but allow.
Bypass (when intentional)
git commit --no-verify -m "..."
Ad-hoc scan (without commit)
$SKILLS/tools/security-precommit-check/scripts/scan.sh --all
$SKILLS/tools/security-precommit-check/scripts/scan.sh --files path1 path2
$SKILLS/tools/security-precommit-check/scripts/scan.sh
Selftest — run this after ANY rule edit
$SKILLS/tools/security-precommit-check/scripts/selftest.sh
Every rule in rules/default.txt must fire on a known-positive sample in
rules/selftest-samples.txt (DESCRIPTION|SAMPLE, all values synthetic). The scanner
fails open, in two ways, and only the first is visible:
| Failure | Symptom | Caught by |
|---|
| Invalid regex | scan.sh prints dropping invalid rule regex | the warning — but only if you read it |
| Valid regex that matches nothing | nothing at all — the rule looks like coverage | only the selftest |
The second class is why this exists. \| inside a group is a LITERAL pipe in ERE, not
alternation, so (api[_-]?key\|secret[_-]?key) matched only the text api_key|secret_key
— dead, silently, for its whole life. Add a positive control with every new rule; a rule
without one fails the selftest.
The selftest also asserts benign lines trip no BLOCK rule (an over-broad rule teaches
people to --no-verify, which costs more than it saves) and that the generic sk- rule
does not shadow the vendor-specific Anthropic/OpenAI ones.
Rule format
SEVERITY|PATTERN|DESCRIPTION
SEVERITY = BLOCK (rejects commit) or WARN (prints warning, exit 0)
PATTERN = extended-regex passed to grep -E
DESCRIPTION = human-readable label (printed in scanner output; the selftest's sample key)
Lines starting with # and blank lines are ignored.
🔴 POSIX ERE only — no PCRE. No (?!…) lookahead, no (?=…), no \d/\w shorthand
you rely on. Write | for alternation unquoted — the parser takes SEV as the first
field and DESC as the last, so a pattern may contain pipes freely; escaping them as \|
silently turns them into literal characters.
What's in rules/default.txt (universal)
| Severity | Category | Examples |
|---|
| BLOCK | API keys | Anthropic / OpenAI / GitHub PAT / Slack / AWS / Stripe |
| BLOCK | Connection strings | host:port:user:pass proxy strings, postgres / mysql / mongodb URLs with embedded password |
| BLOCK | Private keys | PEM private keys, JWT tokens |
| WARN | Personal paths | /Users/{name}/... and /home/{name}/... |
| WARN | Hardcoded literals | password = "..." and api_key = "..." patterns |
What goes in your .security-precommit-rules.txt (per-repo)
| Repo type | Example rules |
|---|
| Public skill / template repo (e.g. cross-border-ecommerce-skills) | BLOCK / WARN: founder real name, project-specific competitor names, internal Org-only hostnames |
| Private project repo | Mostly empty — project-specific terms ARE expected here. Maybe BLOCK known-leaked credentials so re-paste fails |
| Open-source library | WARN founder email; BLOCK known internal hostnames |
| Engineering platform repo (e.g. internal services) | BLOCK production hostnames / DB URLs; WARN dev hostnames |
See templates/security-precommit-rules.txt.example for a starting point.
Layered defense — relationship to other security practices
This hook is layer 3. It does NOT replace:
- Layer 1 — Rotate secrets when leaked. A hook can't fix what's already on a remote. If you find a leaked credential, rotate it AT THE PROVIDER, not just commit a redaction.
- Layer 2 — Configure git author identity.
git config user.name / user.email is set per-commit; this scanner doesn't touch that. Ensure your ~/.gitconfig uses an anonymized noreply email if you don't want your real email in commit metadata.
- Layer 3 — This hook. Prevents accidental commits of new secrets / identity strings going forward.
- Layer 4 — History hygiene. If past commits have secrets,
git filter-repo + force-push is the only way to scrub them. Reserve for cases where rotation isn't enough (e.g. permanent credentials).
False-positive handling
If the scanner BLOCKs a legit string:
- First, double-check it's actually OK to commit. Most "false positives" are real near-misses worth fixing (env-var instead of literal, etc.).
- If genuinely OK:
git commit --no-verify for that one commit; don't disable the hook.
- If a pattern false-positives consistently: tune the regex in
default.txt (PR back to this skill repo) or shadow it via a more specific WARN in your per-repo rules.
Caveats / known limitations
- Pattern-based scanning will miss base64-encoded secrets, secrets concatenated from multiple variables, and binary-encoded credentials. This skill catches the obvious paste-from-clipboard cases — it's not a full secret-scanning solution like TruffleHog / git-secrets / gitleaks.
- The
grep -E engine doesn't support negative lookahead, so some "exclude localhost" rules are approximated via positive constraints (e.g. requiring user/pass length ≥ 8).
- Hook only fires on
git commit. git push --force of an already-committed bad blob won't be caught — keep secrets out at commit time.
Related
- For full repo-history scanning of past leaks, see
gitleaks or trufflehog.
- For automated rotation workflows after a leak: out of scope for this skill.