From 7277b3698e7134489e7668ce2e92731bb1c78870 Mon Sep 17 00:00:00 2001 From: Spyros Gasteratos Date: Fri, 7 Aug 2026 17:48:58 +0100 Subject: [PATCH] ci: make TruffleHog output actionable and drop the Lob false positive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The secret-scanning gate failed on PR #259 with a single line: Found verified Lob result 🐷🔑 No file, no line, no match. The trigger turned out to be a Python test function name, src/poly/tests/metrics_test.py:321: def test_add_duplicate_metric_friendly_error(...) Two independent problems. 1. The finding was noise. TruffleHog's Lob detector uses the pattern `\b((live|test)_[a-zA-Z0-9_]{35})\b` with no keyword-proximity requirement, and its Keywords() prefilter is literally {"live_", "test_"} — the pytest naming convention. So every chunk in the test suite reaches an ungated regex that matches any 40-character `test_*` identifier. It also counts HTTP 403 and 422 from api.lob.com as "verified", which is how a function name came to be reported as a verified secret. Scanning all of main's history with 3.96.0 returns 85 findings, every one of them Lob; excluding the detector takes that to zero. main already contains 36 more identifiers that match, so this would have recurred on any PR touching those lines. ADK does not use Lob, so --exclude-detectors=lob costs no coverage. The other 19 ungated detectors whose prefilter keywords appear in this repo (twilio, uri, redis, ftp, mongodb, launchdarkly, sendgrid, salesforce and friends) were checked against the source tree and have no matches and no near-misses. 2. The output was unactionable regardless. The action always passes --github-actions, whose printer emits only the detector name and verification status; file and line go into annotation metadata that never renders in the log, and the match is never printed at all. Since --json outranks --github-actions in TruffleHog's printer selection, a diagnostic step now re-runs the same scan on failure and reports file, line, commit, author and a 12-character match prefix — enough to triage, not enough to use. That step exits 0; the gate above has already failed the job. Also pins the scanner image. The action was SHA-pinned but its `version` input defaulted to `latest`, so the binary doing the scanning floated independently of the pin. --- .github/workflows/secret-scanning.yml | 42 ++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/.github/workflows/secret-scanning.yml b/.github/workflows/secret-scanning.yml index a107d13c..086c6842 100644 --- a/.github/workflows/secret-scanning.yml +++ b/.github/workflows/secret-scanning.yml @@ -20,9 +20,49 @@ jobs: fetch-depth: 0 - name: Scan for secrets + id: scan uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0 with: - extra_args: --results=verified,unknown + # Tag as published on ghcr — no leading "v" (v3.96.0 is a 404 there, + # even though the action's own git tag does carry the v). + version: 3.96.0 + # --exclude-detectors=lob: the Lob detector's pattern is + # `\b((live|test)_[a-zA-Z0-9_]{35})\b` with no keyword requirement, so it + # matches any 40-character `test_*` Python identifier. It also treats + # HTTP 403/422 from api.lob.com as "verified", so those match as verified + # secrets. ADK does not use Lob, so there is no coverage to lose. + extra_args: --results=verified,unknown --exclude-detectors=lob + + # The action runs TruffleHog with --github-actions, whose printer emits only + # "Found verified result" — the file and line go into annotation + # metadata that never renders in the log, and the match is never printed at + # all. That is not enough to tell a leak from a false positive. Re-run on + # failure with --json and report file, line, commit and a truncated match. + # Only a 12-char prefix is printed: enough to identify the string, not to + # use it. This step exits 0 — the gate above already failed the job. + - name: Report what was flagged + if: failure() && steps.scan.outcome == 'failure' + env: + BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + HEAD: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + run: | + docker run --rm -v .:/tmp -w /tmp \ + ghcr.io/trufflesecurity/trufflehog:3.96.0 \ + git file:///tmp/ \ + --since-commit "$BASE" \ + --branch "$HEAD" \ + --no-update \ + --json \ + --results=verified,unknown \ + --exclude-detectors=lob \ + | jq -r 'select(.DetectorName) | [ + "──────────────────────────────────────────", + "detector: \(.DetectorName) [\(if .Verified then "verified" else "unverified" end)]", + "file: \(.SourceMetadata.Data.Git.file)", + "line: \(.SourceMetadata.Data.Git.line)", + "commit: \(.SourceMetadata.Data.Git.commit[0:12]) by \(.SourceMetadata.Data.Git.email)", + "match: \(.Raw[0:12])… (\(.Raw | length) chars)" + ] | join("\n")' # Direct pushes to main skip PR review entirely, so the Actions tab may be # the only signal. Open an issue and assign the pusher so it can't be missed.