Skip to content

ci(security): pin the TruffleHog scanner, not just its wrapper - #15

Merged
cs3gallery merged 2 commits into
mainfrom
ci/pin-trufflehog-scanner-version
Jul 31, 2026
Merged

cs3gallery merged 2 commits into
mainfrom
ci/pin-trufflehog-scanner-version

Conversation

@cs3gallery

Copy link
Copy Markdown
Contributor

Closes #14.

What

Adds version: v3.96.0 to the secret-scan step in .github/workflows/ci.yml, and rewrites the comment above it to describe what is actually pinned.

Why

uses: trufflesecurity/trufflehog@v3.96.0 pins the composite wrapper. It does not pin the scanner. The action's version input defaults to latest, so the wrapper pulled ghcr.io/trufflesecurity/trufflehog:latest on every run — meaning the binary that decides whether this gate passes was a mutable ref. The run log on #6 shows it: Downloaded newer image for ...:latest.

The comment above that pin argued a mutable ref is a remote-code-execution path into CI. That argument applies with more force to a container running with the repository checked out at fetch-depth: 0, and the pin never closed it.

The second-order problem is the one that actually matters: the gate can stop firing without anyone noticing. Green is ambiguous here — it is what both "no verified secrets" and "the scanner no longer checks" look like. This is how an upstream exit-code change in v3.90.9 reached this workflow with no pull request.

Note for whoever bumps this next

Dependabot maintains the uses: ref (.github/dependabot.yml, github-actions ecosystem). It has no knowledge of the version: input. So a future Dependabot bump will move one pin and leave the other behind, and the step will silently go back to running a different scanner than the tag says. The comment in the workflow says this; it is worth remembering when reviewing the next bump.

What this does not fix

The drift is closed. The gap is not.

Article VIII requires security controls be verified by test rather than asserted in prose, and this control still is not. A positive control — a fixture a working scanner must fail on — is not straightforward here, because --only-verified by design only fails on credentials it can actively confirm are live, and a live credential cannot be committed to test it.

I have not resolved that, and I would rather say so than close #14 implying the control is now proven. If you want it pursued, the plausible directions are a scoped throwaway credential minted and revoked inside the job, or a separate job that runs the scanner without --only-verified against a known fixture and asserts a non-zero exit. Both deserve their own issue and their own thought.

Josh Schall added 2 commits July 30, 2026 22:15
`uses: trufflesecurity/trufflehog@v3.96.0` pins the composite action. It does
not pin the scanner. The action's `version` input defaults to `latest`, so the
wrapper pulled ghcr.io/trufflesecurity/trufflehog:latest on every run — the
binary that decides whether the secret-scan gate passes was a mutable ref.

The comment above the pin argued that a mutable ref is a remote-code-execution
path into CI. That argument applies with more force to a container running with
the repository checked out at fetch-depth 0, and the pin never closed it.

The second-order problem is worse: the gate could stop firing without anyone
noticing. Green is ambiguous here — it is what both "no verified secrets" and
"scanner no longer checks" look like. This is how an upstream exit-code change
in v3.90.9 reached this workflow with no pull request.

Setting `version` explicitly makes the two pins equal and the step reproducible.
Note that Dependabot maintains the `uses:` ref and has no knowledge of the
`version` input, so the two must be moved together; the comment says so.

This closes the drift, not the whole gap. A positive control — a fixture a
working scanner must fail on — is not straightforward under --only-verified,
which by design only fails on credentials it can confirm are live. Article VIII
wants this control verified by test rather than asserted; it still is not.

Closes #14
The container image is tagged 3.96.0; only the action is tagged v3.96.0.
Passing the v form failed with 'manifest unknown' and exit 125.

That failure is itself the evidence the pin now works: before this change the
version input defaulted to latest and a bad value could not have surfaced,
because the input was never reaching the image reference.
@cs3gallery

Copy link
Copy Markdown
Contributor Author

Correction pushed, and the round trip is worth recording because it is the closest thing to a test this change has.

The first attempt used version: v3.96.0, matching the action's tag. It failed:

Unable to find image 'ghcr.io/trufflesecurity/trufflehog:v3.96.0' locally
docker: Error response from daemon: manifest unknown
##[error]Process completed with exit code 125

The container image is tagged 3.96.0; only the action is tagged v3.96.0. Corrected in 2nd commit, and the workflow comment now warns about the prefix.

That failure is the evidence the pin is real. Before this PR, version defaulted to latest and a bad value could not have surfaced at all, because the input was never reaching the image reference. A wrong pin producing a hard failure means the input now controls what runs.

Current run, all four checks green:

Status: Downloaded newer image for ghcr.io/trufflesecurity/trufflehog:3.96.0

3.96.0 rather than latest, which is the whole point of the change. Confirmed 3.96.0 is the highest semver tag published to ghcr.io/trufflesecurity/trufflehog (enumerated all 1041 tags), so the pin matches the action ref rather than lagging it.

Note the failure mode is fail-closed: a wrong version stops the job rather than silently skipping the scan. That is the right direction for a gate, and it is the opposite of the behaviour this PR fixes.

@cs3gallery
cs3gallery merged commit 2f7528c into main Jul 31, 2026
4 checks passed
@cs3gallery
cs3gallery deleted the ci/pin-trufflehog-scanner-version branch July 31, 2026 22:54
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.

Secret-scan action pin does not pin the scanner that decides pass/fail

1 participant