From b70594c4e950b388bf0e8d55e2149859ff2c65db Mon Sep 17 00:00:00 2001 From: Josh Schall Date: Thu, 30 Jul 2026 22:15:57 -0600 Subject: [PATCH 1/2] ci(security): pin the TruffleHog scanner, not just its wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 --- .github/workflows/ci.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f6ee6b..cb23c09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,21 @@ jobs: fetch-depth: 0 - name: Scan for committed secrets - # Pinned to a release tag. Never use @master or @main for a third-party - # action: a mutable ref is a remote-code-execution path into CI. - # Dependabot keeps this pin current (.github/dependabot.yml). + # Two pins, not one. `uses:` pins the composite wrapper; `version:` pins + # the scanner the wrapper actually runs. The action's `version` input + # defaults to `latest`, so without the second pin this step downloads + # ghcr.io/trufflesecurity/trufflehog:latest on every run — the code that + # decides whether the job passes would be a mutable ref, which is the + # remote-code-execution path this comment used to claim was closed. It + # also meant an upstream change to exit-code behaviour reached this + # workflow with no pull request. Keep the two versions equal. + # + # Dependabot updates the `uses:` ref (.github/dependabot.yml); it does + # not know about `version:`. Update both together. See issue #14. uses: trufflesecurity/trufflehog@v3.96.0 with: path: ./ + version: v3.96.0 # --only-verified keeps this a blocking gate rather than a noise # generator: it fails on credentials TruffleHog can actively confirm # are live. Unverified matches are deliberately not fatal, because From f76ae5eb8de249f90a09744f578eeadbdfc389d6 Mon Sep 17 00:00:00 2001 From: Josh Schall Date: Thu, 30 Jul 2026 22:19:08 -0600 Subject: [PATCH 2/2] ci(security): use the image tag form for the TruffleHog version pin 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. --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb23c09..1f85f28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,10 +87,15 @@ jobs: # # Dependabot updates the `uses:` ref (.github/dependabot.yml); it does # not know about `version:`. Update both together. See issue #14. + # + # Mind the prefix: the action is tagged `v3.96.0`, the container image is + # tagged `3.96.0`. Passing the `v` form here fails the job with + # `manifest unknown` and exit 125 — noisy, but fail-closed, which is the + # right direction for a security gate. uses: trufflesecurity/trufflehog@v3.96.0 with: path: ./ - version: v3.96.0 + version: 3.96.0 # --only-verified keeps this a blocking gate rather than a noise # generator: it fails on credentials TruffleHog can actively confirm # are live. Unverified matches are deliberately not fatal, because