Skip to content

fix(scan): let the history scan see every path, and make the SOPS check read the values - #1187

Merged
Gerrrt merged 2 commits into
mainfrom
review/gitleaks-history-sees-every-path
Oct 14, 2026
Merged

Gerrrt merged 2 commits into
mainfrom
review/gitleaks-history-sees-every-path

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Oct 14, 2026

Copy link
Copy Markdown
Owner

What changed

Two verification checks that claimed more than they proved, found during a repository review.

gitleaks. The path allowlists for gitignored files (.rendered/, certificates/, .env, ansible/.ansible/, ansible/.collections/) move out of .gitleaks.toml into a new .gitleaks-worktree.toml, which [extend]s it. The working-tree scans (make scan's first line, scripts/validate.sh, the workflow's "gitleaks — working tree" step) now read the worktree file. The history scan (scripts/gitleaks-history.sh, untouched) keeps reading .gitleaks.toml, which no longer allowlists anything gitignored. Comments in .gitleaks.toml, ci.yml and scripts/check-tracked-artefacts.sh say why, and docs/security.md § Known historical exposure records the change under its "a history scan that passes is the evidence the purge worked" sentence.

SOPS. scripts/check-sops-encrypted.sh now also requires every value above the sops: block to be ENC[...], not only that the block exists. It gains --self-test with eleven fixtures, which scripts/self-tests.sh discovers automatically, and rejects any other argument.

Why

A gitleaks path allowlist applies to every commit a history scan walks, not only to the working tree. With certificates/ allowlisted in the one shared config, the purged CA key named in SECURITY.md would never have been reported if it reappeared in history, yet docs/security.md cites the passing history scan as the evidence the purge worked. The repo's own comment on the Ansible paths already noted that "an allowlisted path is invisible to the history scan as well"; this makes that true only of the working tree.

The SOPS check tested for the metadata block alone. A value pasted back in the clear leaves the block in place, so such a file passed this check, passed gitleaks (secrets/*.sops.yaml is path-allowlisted there because ciphertext trips the entropy rules), and make render would have copied it through. sops itself refuses the file on its MAC, but only on a host holding the key, which the push does not.

Blast radius

None at runtime. make validate on a deployed host behaves as before: the worktree config carries the same allowlists the shared file did. The history scan becomes stricter, which on a clean history changes nothing.

  • No change to network segmentation or firewall rules
  • No new port published to a VLAN that could not already reach the service
  • No credential added outside secrets/*.sops.yaml

Verification

Run here (no Docker daemon, so gitleaks itself could not be run; CI's two scans are the test of the config split):

$ python3 -c 'import tomllib; ...'   # both files parse; worktree extends .gitleaks.toml, 5 paths; base keeps 2 paths, 5 rules
$ bash -n scripts/check-sops-encrypted.sh scripts/validate.sh scripts/check-tracked-artefacts.sh
$ ./scripts/check-sops-encrypted.sh --self-test   # 11 PASS, exit 0
$ ./scripts/check-sops-encrypted.sh               # 8 SOPS file(s), all encrypted
$ ./scripts/check-sops-encrypted.sh bogus         # usage, exit 2
$ ./scripts/self-tests.sh --list | grep -c check-sops-encrypted   # 1
$ ./scripts/check-tracked-artefacts.sh            # clean

The awk is POSIX (the hosts run mawk; the first draft used a gawk-only match() form, which mawk rejected and which would have passed every file silently, so a failed read is now itself a failure).

  • make validate passes (CI)
  • Deployed to the lab and confirmed working (not applicable: checks only; converge on prometheus will run the new validate.sh)
  • Docs updated (docs/security.md)

🤖 Generated with Claude Code

https://claude.ai/code/session_013Dchg9vmfKeQd7uzRzPcLR


Generated by Claude Code

Copilot AI balanced review requested due to automatic review settings October 14, 2026 05:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The self-test mode currently accepts trailing arguments despite the documented rejection requirement.

1 open finding
What changed in this PR

Splits Gitleaks configurations by scan scope and strengthens SOPS ciphertext validation.

Changes:

  • Uses a dedicated working-tree Gitleaks configuration while keeping history scans unrestricted.
  • Validates SOPS values and adds fixture-based self-tests.
  • Documents the security rationale and historical exposure implications.
File Description
.gitleaks.toml Removes gitignored-path exclusions from history scans.
.gitleaks-worktree.toml Adds working-tree-only path exclusions.
.github/​workflows/​ci.yml Uses the worktree configuration in CI.
Makefile Separates worktree and history scan configurations.
scripts/​validate.sh Uses the worktree configuration locally.
scripts/​check-tracked-artefacts.sh Updates configuration-split documentation.
scripts/​check-sops-encrypted.sh Validates ciphertext values and adds fixtures.
docs/​security.md Records the stricter history-scan behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/check-sops-encrypted.sh Outdated
…ck read the values

Two checks that claimed more than they proved.

gitleaks path allowlists apply to every commit a history scan walks, not
only to the working tree. .gitleaks.toml allowlisted certificates/,
.rendered/, .env and the installed Ansible collections so a deployed
host's `make validate` stays green, and the same file fed the history
scan — so the purged CA key under certificates/ was exactly the path the
scan docs/security.md calls the evidence of the purge could never report.
The gitignored paths move to .gitleaks-worktree.toml, which extends
.gitleaks.toml and is what the working-tree scans read; the history scan
keeps .gitleaks.toml, now with no allowlist for anything gitignored.
scripts/check-tracked-artefacts.sh is still what keeps those paths out of
the tree, and in history a tracked copy is now a finding as well.

scripts/check-sops-encrypted.sh passed any file carrying a `sops:` block.
A value pasted back in the clear leaves the block in place, passes
gitleaks (the ciphertext is path-allowlisted), and `make render` would
copy it through. It now also requires every value above the block to be
ENC[...], in POSIX awk because the hosts run mawk, with a failed read
counted as a failure and eleven fixtures under --self-test, which
scripts/self-tests.sh discovers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Dchg9vmfKeQd7uzRzPcLR
@Gerrrt
Gerrrt force-pushed the review/gitleaks-history-sees-every-path branch from 1033681 to e2dcb69 Compare October 14, 2026 05:19
`--self-test bogus` ran the fixtures and exited 0; the usage guard
below only saw an argument that was not --self-test. Review finding on
#1187.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Dchg9vmfKeQd7uzRzPcLR
@Gerrrt
Gerrrt merged commit e5211db into main Oct 14, 2026
8 checks passed
@Gerrrt
Gerrrt deleted the review/gitleaks-history-sees-every-path branch October 14, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants