Files
command-center/tests/secret-gate.bats
T
Frank 71601f72a8 fix(secret-gate): a documented fake id must not stop the machine tier
The live-sync heartbeat failed three hourly ticks (22:25, 23:27, 00:28) because a
managed skill that documents how to write a secret gate quotes the vendor's
all-zeros placeholder access-key id, and the shape rule fired on the
documentation. The machine tier is prose ABOUT credentials — the gate's own header
says a gate that fires there gets overridden until it means nothing — and a
heartbeat that cannot commit is a fleet that silently stops syncing config.

- shape hits are judged on the matched token, not the line: a documented fake is
  inert, one live token sharing the file still blocks. The match is judged with
  its following context, because a fixed quantifier truncates `EXAMPLE` to `EXA`
  and the placeholder would vanish from what is examined.
- assignment values keep the stricter rule from the previous commit: a comment on
  the same line cannot vouch for the token beside it.
- `ALLOW_SECRETS` remains the only override; nothing here widens what is scanned,
  it only stops flagging text that was never a credential.

Verified against the payload that is stuck right now: the skill page that the
heartbeat cannot commit passes the fixed hook and was refused by the live one.

Refs #60
2026-10-21 00:59:18 -07:00

205 lines
7.9 KiB
Bash
Executable File

#!/usr/bin/env bats
# tests/secret-gate.bats — black-box tests for .githooks/pre-commit
#
# Contract: a commit whose staged content contains a credential is blocked
# (non-zero exit, stderr names the file); a commit whose content is benign
# configuration or prose about credentials is allowed. The gate is copied into a
# scratch repo so git itself invokes it — no sourcing, no internal state.
setup() {
HOOK="$BATS_TEST_DIRNAME/../.githooks/pre-commit"
REPO="$(mktemp -d)/g"
mkdir -p "$REPO/.git/hooks"
git init -q "$REPO"
git -C "$REPO" config user.email gate@test
git -C "$REPO" config user.name gate
cp "$HOOK" "$REPO/.git/hooks/pre-commit"
chmod +x "$REPO/.git/hooks/pre-commit"
}
teardown() { rm -rf "$(dirname "$REPO")"; }
# try_commit <file> <content> -> sets status/output from the real commit attempt
try_commit() {
local file="$1" content="$2"
mkdir -p "$REPO/$(dirname "$file")"
printf '%s\n' "$content" > "$REPO/$file"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "attempt: $file"
}
@test "blocks a low-entropy password in a .env file" {
try_commit "postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
[ "$status" -ne 0 ]
[[ "$output" == *"SECRET GATE"* ]]
[[ "$output" == *"postgres.env"* ]]
}
@test "blocks a credential in a file type the extension whitelist used to skip" {
try_commit "app.conf" '[app]
secret_key = "rotatedvalue99"'
[ "$status" -ne 0 ]
[[ "$output" == *"app.conf"* ]]
}
@test "blocks a token shape in a plain text file" {
try_commit "notes.txt" "backup remote: ghp_$(printf 'a%.0s' {1..36})"
[ "$status" -ne 0 ]
[[ "$output" == *"notes.txt"* ]]
}
@test "blocks a bcrypt hash" {
try_commit "dump.txt" 'basicauth: $2b$14$'"$(printf 'a%.0s' {1..53})"
[ "$status" -ne 0 ]
[[ "$output" == *"dump.txt"* ]]
}
@test "blocks a credential in a quadlet unit" {
try_commit "db.container" '[Container]
Environment=POSTGRES_PASSWORD=devpostgres2026'
[ "$status" -ne 0 ]
[[ "$output" == *"db.container"* ]]
}
@test "prose about credentials in markdown is still allowed" {
try_commit "docs/runbook.md" 'The GITEA_TOKEN moved out of .zshrc into secrets.env.
Set password: user.set_password(value) and api_key = "sk-no-key-required".'
[ "$status" -eq 0 ]
}
@test "placeholder and indirected values in config files are allowed" {
try_commit "app.conf" '[db]
PASSWORD = CHANGEME
SECRET_KEY = ${VAULT_SECRET}
api_key = "sk-no-key-required"
token_file = <operator_token>'
[ "$status" -eq 0 ]
}
@test "environment variable indirection is not a secret" {
try_commit "start.sh" '#!/bin/sh
export GITEA_TOKEN="$GITEA_TOKEN"
export API_KEY_FILE=/run/secrets/api_key'
[ "$status" -eq 0 ]
}
@test "binary content never trips the gate" {
head -c 4096 /dev/urandom > "$REPO/wall.jpg"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "binary"
[ "$status" -eq 0 ]
}
@test "ALLOW_SECRETS still overrides, and says so" {
try_commit "postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
[ "$status" -ne 0 ]
mkdir -p "$REPO/.git/hooks"
run env ALLOW_SECRETS=1 git -C "$REPO" commit -q -m "override"
[ "$status" -eq 0 ]
[[ "$output" == *"ALLOW_SECRETS set"* ]]
}
@test "this repo's ignore policy quarantines env files at any name" {
repo="$BATS_TEST_DIRNAME/.."
run git -C "$repo" check-ignore -q --no-index dotfiles/db/postgres.env
[ "$status" -eq 0 ]
run git -C "$repo" check-ignore -q --no-index dotfiles/app/frank-job-hunt.env
[ "$status" -eq 0 ]
run git -C "$repo" check-ignore -q --no-index dotfiles/bat/config
[ "$status" -ne 0 ]
}
@test "the gate refuses to pass when it cannot read the staged diff" {
# A gate that swallows a git error reports success on every commit. This is not
# hypothetical: the 2026-10-20 rewrite passed `-I` to git diff, which is not a
# valid flag there, and the empty file list made every secret commit sail through.
nodir="$(mktemp -d)"
run bash -c "cd '$nodir' && '$HOOK'"
rm -rf "$nodir"
[ "$status" -ne 0 ]
[[ "$output" == *"cannot read the staged diff"* ]]
}
@test "deleting a credential is never blocked" {
# The commit that removes a secret must always be possible, or the gate holds a
# leak hostage. It did exactly that on 2026-10-20: matching removed lines made
# the redaction commit look like the leak. Seed with the documented override,
# then remove with an ordinary commit.
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/db.conf"
run git -C "$REPO" add -A
run env ALLOW_SECRETS=1 git -C "$REPO" commit -q -m "seed a leak"
[ "$status" -eq 0 ]
printf '[db]\nPASSWORD = ${VAULT_SECRET}\n' > "$REPO/db.conf"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "redact the leaked password"
[ "$status" -eq 0 ]
}
@test "the gate can commit itself" {
# Fixtures have to look like credentials, so a gate that scans its own test file
# without exemption can never be committed — it holds its own maintenance
# hostage. The exemption is two exact paths, and it must announce itself.
mkdir -p "$REPO/.githooks" "$REPO/tests"
cp "$HOOK" "$REPO/.githooks/pre-commit"
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/tests/secret-gate.bats"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "gate and its fixtures"
[ "$status" -eq 0 ]
[[ "$output" == *"self-test fixtures exempt"* ]]
}
@test "a credential hidden in some other test file is still caught" {
# The exemption is a filename list, not the tests/ directory.
mkdir -p "$REPO/tests"
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/tests/other.bats"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "smuggle"
[ "$status" -ne 0 ]
[[ "$output" == *"tests/other.bats"* ]]
}
@test "a credential under a path git quotes is still read" {
# core.quotePath is on by default, so `git diff --name-only` prints "caf\303\251/…" for a
# non-ASCII directory. Feed that quoted string back to `git diff -- <path>` and the diff is
# empty — and a gate that skips an unreadable file reports success. This is the fail-closed
# promise breaking on its own terms, not a cosmetic quoting complaint.
try_commit "café/postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
[ "$status" -ne 0 ]
[[ "$output" == *"postgres.env"* ]]
}
@test "a placeholder word elsewhere on the line does not exempt a live value" {
# The rule is about the VALUE. `# example` in a comment says nothing about the token the
# line assigns, but a whole-line placeholder filter lets the comment vouch for the secret.
try_commit "app.env" 'API_TOKEN=hunter2corp2026 # example, placeholder, changeme'
[ "$status" -ne 0 ]
[[ "$output" == *"app.env"* ]]
}
@test "each hit is attributed to the file it came from" {
# The report is how the next agent finds the leak. If one file's last hit line runs into the
# next file's name, the audit starts at a clean file and the leaker keeps its name.
mkdir -p "$REPO/a" "$REPO/b"
printf 'A_PASSWORD=hunter2corp2026\n' > "$REPO/a/one.env"
printf 'B_PASSWORD=hunter2corp2026\n' > "$REPO/b/two.env"
run git -C "$REPO" add -A
run git -C "$REPO" commit -q -m "two leakers"
[ "$status" -ne 0 ]
printf '%s\n' "$output" | grep -qx ' a/one.env'
printf '%s\n' "$output" | grep -qx ' b/two.env'
! printf '%s\n' "$output" | grep -qE 'hunter2corp2026 +b/two\.env'
}
@test "a documented fake id in prose is committable; a live-shaped id in the same file is not" {
# Measured on blankpad 2026-10-21: the live-sync heartbeat failed three ticks in a row
# because a managed-skill page documents how to write a secret gate and quotes AWS's own
# placeholder id. A gate that blocks the machine tier over an inert example gets worked
# around, and a workaround is the hole. The exemption reads the matched token, so a real
# id in the same file still blocks.
try_commit "docs.md" 'Inert fixtures look like AKIAIOSFODNN7EXAMPLE or AKIA0000000000000EXAMPLE.'
[ "$status" -eq 0 ]
try_commit "docs.md" 'Inert fixture AKIAIOSFODNN7EXAMPLE and a live one AKIAQZR4W7DBPCFG7V3Q.'
[ "$status" -ne 0 ]
[[ "$output" == *"docs.md"* ]]
}