# Security Precommit Check

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

- Skill: `noique/security-precommit-check` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add noique/security-precommit-check`
- Raw SKILL.md: https://api.skillmd.com/api/skills/noique/security-precommit-check/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: noique (https://skillmd.com/u/noique)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/noique/security-precommit-check

---


# 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)

```bash
# Replace this path with where you cloned cross-border-ecommerce-skills
SKILLS=~/Documents/Code/cross-border-ecommerce-skills
cd /path/to/your/repo
$SKILLS/tools/security-precommit-check/scripts/install-hook.sh
```

The installer:
1. Writes `.git/hooks/pre-commit` (idempotent — re-run to refresh)
2. 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)

```bash
git commit --no-verify -m "..."
```

## Ad-hoc scan (without commit)

```bash
# Scan all tracked files (good for initial audit on existing repos)
$SKILLS/tools/security-precommit-check/scripts/scan.sh --all

# Scan specific files
$SKILLS/tools/security-precommit-check/scripts/scan.sh --files path1 path2

# Scan staged files (default — same as the hook)
$SKILLS/tools/security-precommit-check/scripts/scan.sh
```

## Selftest — run this after ANY rule edit

```bash
$SKILLS/tools/security-precommit-check/scripts/selftest.sh   # exit 0 = every rule is alive
```

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:

1. **First**, double-check it's actually OK to commit. Most "false positives" are real near-misses worth fixing (env-var instead of literal, etc.).
2. If genuinely OK: `git commit --no-verify` for that one commit; don't disable the hook.
3. 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`](https://github.com/gitleaks/gitleaks) or [`trufflehog`](https://github.com/trufflesecurity/trufflehog).
- For automated rotation workflows after a leak: out of scope for this skill.

