fix(secret-gate): a path git quotes, a placeholder comment, a glued report line

Three ways the fail-closed gate failed open, all reproduced by new tests first:

- `git diff --name-only` quotes any path it cannot print as UTF-8
  (core.quotePath, on by default). The quoted string was handed back to
  `git diff -- <path>`, which matches nothing, and an empty diff is `continue`d —
  so a credential under `café/` committed cleanly. The list is now NUL-delimited
  and read from a temp file, because command substitution deletes NUL bytes and
  the names must survive to the loop that reads them.
- The placeholder filter matched the whole added line, so `API_TOKEN=<live>  #
  example, placeholder, changeme` was exempt on the strength of its own comment.
  The filter now reads the assigned value only — the token up to whitespace or
  `#`. Truncation errs toward blocking; ALLOW_SECRETS remains the way out.
- `report+=` ended on a command substitution, so the next file's name was glued
  onto the previous file's last hit line and the report blamed the wrong file.

Three tests, all red before the change: a quoted path, a placeholder comment
beside a live value, and two leaking files each named on their own line.

Refs #60
This commit is contained in:
Frank committed 2026-10-21 00:55:47 -07:00
1 parent 06f45e83c1
commit 812be6a0a6
2 files changed
+68 -10

No files matched your search

+32
View File
@@ -157,3 +157,35 @@ export API_KEY_FILE=/run/secrets/api_key'
[ "$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'
}