Files
command-center/agent/managed-skills/git-secret-gate-hook/SKILL.md
T
Frank 8af40c402b chore(agent): commit the machine tier and the doctrine text the checkout was already running
Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and
`merge --ff-only` refuses while tracked paths are dirty. Discard is not available here
(workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed.

- `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already
  byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new
  prose — the commit exists so the trunk merge can proceed.
- `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat
  owns (AGENTS.md § Live-sync tiers).

`ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the
only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the
inert all-zero AWS example id — and they appear inside the two skill pages that teach
that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged
gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal
dies with this landing. No `--no-verify` was used: the gate ran and its verdict is
recorded here.

Refs #61
2026-10-21 01:44:51 -07:00

3.4 KiB

name, description
name description
git-secret-gate-hook Write a fail-closed pre-commit secret-gate hook (staged-content scan) with the grep/ERE traps that kill naive versions, plus its bats fixture test.

Git secret-gate pre-commit hook

Use when a repo promises a "pre-commit secret gate" but none exists, or when porting the gate to another dank repo. Ships with three traps already paid for.

Shape

  • Track the hook at hooks/pre-commit (chmod +x). Git never self-installs hooks — wire per clone: git config core.hooksPath hooks — run it in the MAIN checkout once; core.hooksPath lives in the common git dir config, so every linked worktree inherits it.
  • Do NOT put hooks in package.json files — an installed plugin under node_modules never runs git.
  • Scan staged content (git show ":$f"), not the working tree: that is what lands in history.
  • Denylist by path (README, docs/, tests/, .env.example) — files whose job is naming secrets or holding fixture dummies produce theatre, not safety.
  • Never echo the matched secret; print file + line number only.
  • Red-first test: fixture repo + cp the real hook in + git config core.hooksPath hooks, then drive REAL git commit attempts: credential-shaped file must exit non-zero AND git log must show no new commit (block, not warn); clean commit passes; hook must survive committing itself.

The three traps (each cost a red test)

  1. -- is a ONE-SHOT end-of-options marker. grep -nE -- 'p1' -- 'p2' treats everything after the first -- as FILE NAMES: grep: --: No such file or directory, rc=2. Multi-pattern = repeat -e: grep -nE -e 'p1' -e 'p2'.
  2. if hits=$(grep ...) is fail-OPEN on rc>=2 (rc=2 is truthy, hits empty). Capture rc=$? explicitly: rc>=2 must BLOCK with "pattern error, refusing to pass unchecked" — a gate whose regex silently dies is worse than none.
  3. POSIX ERE (grep -E) gotchas: no (?:...) groups (use plain (...)); a [:-] inside a bracket starts a collating element [: and dies with "repetition-operator operand invalid" — put - FIRST in every class ([-A-Za-z0-9/+=_.:+]). Also -----BEGIN literals: prefer printf-safe fixtures in tests.

Pattern set (shapes, not entropy — say so in the header comment)

-----BEGIN[ A-Z0-9]*(OPENSSH|RSA|DSA|EC|PGP)? ?PRIVATE KEY-----
AKIA[0-9A-Z]{16}
gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{50,}
xox[baprs]-[A-Za-z0-9-]{10,}
https://hooks\.slack\.com/services/[A-Za-z0-9/]+
(aws_secret_access_key|secret(_access)?_key|(api|access|auth)[_-]?key|[a-z_]*token|passwd|password|credential)[[:space:]"']?[[:space:]]*[:=][[:space:]]*["']?[-A-Za-z0-9/+=_.:+]{16,}
(Bearer|token|authorization)[[:space:]]*[:=][[:space:]]*["']?[-A-Za-z0-9._+/]{20,}

Test fixture hygiene

Fake secrets must be inert (RFC 2606 domains, all-zero ids, AKIA0000000000000EXAMPLE) AND must not match the hook's own self-scan. Single quotes in ERE patterns: write '"'"'.

Wiring gotcha (dank worktree policy)

Set core.hooksPath AFTER the merge that lands hooks/ into main — with the config set but the path missing, git fails every commit in worktrees lacking hooks/ ("cannot run hooks/pre-commit: No such file or directory"). Order: merge -> land -> git config core.hooksPath hooks from main checkout.

Reference implementation: dank/dankvoice hooks/pre-commit (PR #19), pinned by tests/gate.bats.