@@ -30,7 +30,7 @@ permissions:
3030
3131env :
3232 # renovate: datasource=github-releases depName=trufflesecurity/trufflehog
33- TRUFFLEHOG_VERSION : 3.89.2
33+ TRUFFLEHOG_VERSION : v3.94.0
3434
3535jobs :
3636 trufflehog-scan :
@@ -43,77 +43,115 @@ jobs:
4343 fetch-depth : 1
4444 persist-credentials : true
4545
46- - name : Fetch base and head commits
46+ - name : Fetch base and head commits (pull_request)
4747 if : github.event_name == 'pull_request'
48- run : git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
48+ env :
49+ PR_BASE_SHA : ${{ github.event.pull_request.base.sha }}
50+ PR_HEAD_SHA : ${{ github.event.pull_request.head.sha }}
51+ run : git fetch --depth=1 origin "${PR_BASE_SHA}" "${PR_HEAD_SHA}"
52+
53+ - name : Fetch base and head commits (merge_group)
54+ if : github.event_name == 'merge_group'
55+ env :
56+ MERGE_GROUP_BASE_SHA : ${{ github.event.merge_group.base_sha }}
57+ MERGE_GROUP_HEAD_SHA : ${{ github.event.merge_group.head_sha }}
58+ run : git fetch --depth=1 origin "${MERGE_GROUP_BASE_SHA}" "${MERGE_GROUP_HEAD_SHA}"
4959
5060 - name : Remove persisted credentials
5161 run : git config --unset-all http.https://github.com/.extraheader
5262
63+ - name : Fetch org-wide TruffleHog exclude patterns
64+ env :
65+ GH_TOKEN : ${{ github.token }}
66+ WORKFLOW_REF : ${{ github.workflow_ref }}
67+ run : |
68+ DEST=/tmp/trufflehog-exclude.txt
69+ REPO=grafana/security-github-actions
70+ FILE_PATH=trufflehog/exclude-paths.txt
71+
72+ # Resolve the ref the calling workflow used (e.g. @main, @feature/branch, @v1).
73+ CALLER_REF="main"
74+ if [[ -n "${WORKFLOW_REF}" ]]; then
75+ CALLER_REF=$(echo "${WORKFLOW_REF}" | sed 's|.*@||; s|^refs/heads/||; s|^refs/tags/||')
76+ fi
77+
78+ LOADED=false
79+ for REF in main "${CALLER_REF}"; do
80+ [[ "$LOADED" == "true" ]] && break
81+ if gh api "repos/${REPO}/contents/${FILE_PATH}?ref=${REF}" \
82+ -H "Accept: application/vnd.github.v3.raw" -o "${DEST}" 2>/dev/null && [[ -s "${DEST}" ]]; then
83+ echo "Loaded exclude patterns from ${REPO}@${REF} (GitHub API)"
84+ LOADED=true
85+ elif curl -fsSL "https://raw.githubusercontent.com/${REPO}/${REF}/${FILE_PATH}" \
86+ -o "${DEST}" 2>/dev/null && [[ -s "${DEST}" ]]; then
87+ echo "Loaded exclude patterns from raw.githubusercontent.com@${REF}"
88+ LOADED=true
89+ fi
90+ done
91+
92+ if [[ "$LOADED" != "true" ]]; then
93+ echo "::warning::Could not fetch TruffleHog exclude patterns from ${REPO} (tried main and ${CALLER_REF}). Scanning without exclusions."
94+ touch "${DEST}"
95+ fi
96+
97+ # Append repo-local .trufflehogignore if present (same Go regex format).
98+ if [[ -f ".trufflehogignore" ]]; then
99+ echo "" >> "${DEST}"
100+ echo "# Repo-local .trufflehogignore" >> "${DEST}"
101+ cat .trufflehogignore >> "${DEST}"
102+ echo "Appended repo-local .trufflehogignore"
103+ fi
104+
105+ echo "--- effective exclude patterns ---"
106+ cat "${DEST}"
107+
53108 - name : Install TruffleHog
54109 run : |
55110 # Download binary directly from GitHub releases for supply chain security
56- VERSION="v${{ env.TRUFFLEHOG_VERSION }} "
111+ echo "Using Trufflehog version: $TRUFFLEHOG_VERSION "
57112 # Auto-detect architecture for cross-platform support
58113 if [[ "$(uname -m)" == "aarch64" ]]; then
59114 ARCH="linux_arm64"
60115 else
61116 ARCH="linux_amd64"
62117 fi
63- BINARY_URL="https://github.com/trufflesecurity/trufflehog/releases/download/${VERSION }/trufflehog_${VERSION #v}_${ARCH}.tar.gz"
118+ BINARY_URL="https://github.com/trufflesecurity/trufflehog/releases/download/${TRUFFLEHOG_VERSION }/trufflehog_${TRUFFLEHOG_VERSION #v}_${ARCH}.tar.gz"
64119
65120 curl -sSfL "${BINARY_URL}" | tar -xz -C /tmp
66121 sudo mv /tmp/trufflehog /usr/local/bin/trufflehog
67122 sudo chmod +x /usr/local/bin/trufflehog
68123 trufflehog --version
69124
70- - name : Create global exclusions
71- run : |
72- # Create centralized exclusion patterns for common false positives
73- cat > /tmp/trufflehog-exclude.txt <<'EOF'
74- # Lock files and checksums (contain hashes, not secrets)
75- path:go\.sum$
76- path:go\.mod$
77- # Dependency manifests (contain URLs that trigger false positives)
78- path:package\.json$
79- path:package-lock\.json$
80- path:pnpm-lock\.yaml$
81- path:yarn\.lock$
82- path:poetry\.lock$
83- path:Pipfile\.lock$
84- path:uv\.lock$
85- path:Cargo\.lock$
86- path:Gemfile\.lock$
87- # Grafana plugin metadata
88- path:grafana\.json$
89- EOF
90-
91- echo "Created global exclusion patterns:"
92- cat /tmp/trufflehog-exclude.txt
93-
94125 - name : Scan for secrets
95126 id : scan
127+ env :
128+ EVENT_NAME : ${{ github.event_name }}
129+ PR_BASE_SHA : ${{ github.event.pull_request.base.sha }}
130+ PR_HEAD_SHA : ${{ github.event.pull_request.head.sha }}
131+ MERGE_GROUP_BASE_SHA : ${{ github.event.merge_group.base_sha }}
132+ MERGE_GROUP_HEAD_SHA : ${{ github.event.merge_group.head_sha }}
96133 run : |
97134 set +e
98135 echo "[]" > results.json
99136
100- if [[ "${{ github.event_name }}" == "pull_request" ]]; then
101- # PR: Scan only changed files (using two-dot diff with explicit base SHA)
102- echo "Scanning changed files in PR..."
103- git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} > changed-files.txt
137+ # Extract non-comment, non-blank patterns for the shell pre-filter.
138+ grep -vE '^\s*#|^\s*$' /tmp/trufflehog-exclude.txt > /tmp/exclude-regexes.txt 2>/dev/null || true
139+
140+ if [[ "${EVENT_NAME}" == "pull_request" ]] || [[ "${EVENT_NAME}" == "merge_group" ]]; then
141+ if [[ "${EVENT_NAME}" == "pull_request" ]]; then
142+ echo "Scanning changed files in PR..."
143+ git diff --name-only "${PR_BASE_SHA}" "${PR_HEAD_SHA}" > changed-files.txt
144+ else
145+ echo "Scanning changed files in merge group..."
146+ git diff --name-only "${MERGE_GROUP_BASE_SHA}" "${MERGE_GROUP_HEAD_SHA}" > changed-files.txt
147+ fi
104148
105149 if [[ -s changed-files.txt ]]; then
106150 while IFS= read -r file; do
107- # Get just the filename
108- filename=$(basename "$file")
109-
110- # Skip excluded files (use case statement for cleaner matching)
111- case "$filename" in
112- go.sum|go.mod|package.json|package-lock.json|pnpm-lock.yaml|yarn.lock|poetry.lock|Pipfile.lock|uv.lock|Cargo.lock|Gemfile.lock|grafana.json)
113- echo "Skipping: ${file} (excluded manifest/lock file)"
114- continue
115- ;;
116- esac
151+ if [[ -s /tmp/exclude-regexes.txt ]] && echo "$file" | grep -qEf /tmp/exclude-regexes.txt 2>/dev/null; then
152+ echo "Skipping: ${file} (matches exclude pattern)"
153+ continue
154+ fi
117155
118156 if [[ -f "${file}" ]]; then
119157 echo "Scanning: ${file}"
@@ -124,7 +162,6 @@ jobs:
124162 echo "No files changed"
125163 fi
126164 else
127- # Push to main: Scan current filesystem
128165 echo "Scanning current filesystem..."
129166 trufflehog filesystem . --exclude-paths /tmp/trufflehog-exclude.txt --concurrency 16 --json --no-update --results=verified,unverified > results.ndjson || true
130167 fi
@@ -141,8 +178,12 @@ jobs:
141178 # Filter out CHANGELOG git hashes if we have results
142179 if jq -e 'length > 0' results.json >/dev/null 2>&1; then
143180 jq '[.[] | select(
144- ((.SourceMetadata?.Data?.Filesystem?.file // .SourceMetadata?.Data?.Git?.file) // "") as $file |
145- !(($file | test("CHANGELOG|HISTORY\\.md|NEWS\\.md"; "i")) and (.Raw | test("^[0-9a-f]{7,40}$"; "i")))
181+ (
182+ (try .SourceMetadata.Data.Filesystem.file catch null) //
183+ (try .SourceMetadata.Data.Git.file catch null) //
184+ ""
185+ ) as $file |
186+ ((($file | test("CHANGELOG|HISTORY\\.md|NEWS\\.md"; "i")) and (.Raw | test("^[0-9a-f]{7,40}$"; "i"))) | not)
146187 )]' results.json > results.json.tmp && mv results.json.tmp results.json
147188 fi
148189 else
@@ -176,26 +217,29 @@ jobs:
176217
177218 - name : Delete resolved comment
178219 if : ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.scan.outputs.total == '0' }}
220+ env :
221+ GH_TOKEN : ${{ github.token }}
222+ GH_REPOSITORY : ${{ github.repository }}
223+ PR_NUMBER : ${{ github.event.pull_request.number }}
179224 run : |
180225 # Find and delete TruffleHog comment if secrets have been resolved
181- COMMENT_ID=$(gh api repos/${{ github.repository }} /issues/${{ github.event.pull_request.number }} /comments --jq '.[] | select(.body | contains("<!-- trufflehog-secret-scan-comment -->")) | .id' | head -1 || echo "")
226+ COMMENT_ID=$(gh api " repos/${GH_REPOSITORY} /issues/${PR_NUMBER} /comments" --jq '.[] | select(.body | contains("<!-- trufflehog-secret-scan-comment -->")) | .id' | head -1 || echo "")
182227
183228 if [[ -n "$COMMENT_ID" ]]; then
184229 echo "Secrets resolved - deleting previous warning comment (ID: ${COMMENT_ID})"
185- gh api -X DELETE /repos/${{ github.repository }} /issues/comments/${COMMENT_ID}
230+ gh api -X DELETE " /repos/${GH_REPOSITORY} /issues/comments/${COMMENT_ID}"
186231 echo "Comment deleted successfully"
187232 else
188233 echo "No existing TruffleHog comment to delete"
189234 fi
190- env :
191- GH_TOKEN : ${{ github.token }}
192235
193236 - name : Generate PR comment
194237 if : ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.scan.outputs.total > 0 }}
195238 id : comment-body
239+ env :
240+ VERIFIED : ${{ steps.scan.outputs.verified }}
241+ UNVERIFIED : ${{ steps.scan.outputs.unverified }}
196242 run : |
197- VERIFIED=${{ steps.scan.outputs.verified }}
198- UNVERIFIED=${{ steps.scan.outputs.unverified }}
199243 TOTAL=$((VERIFIED+UNVERIFIED))
200244
201245 if [[ $TOTAL -eq 0 ]]; then
@@ -212,9 +256,9 @@ jobs:
212256 "- " +
213257 (if .Verified then "**VERIFIED SECRET**" else "**Possible secret**" end) +
214258 " (" + .DetectorName + ") at `" +
215- ((.SourceMetadata? .Data? .Filesystem? .file // .SourceMetadata? .Data? .Git? .file) // "unknown") +
259+ (((try .SourceMetadata.Data.Filesystem.file catch null) // (try .SourceMetadata.Data.Git.file catch null) ) // "unknown") +
216260 ":" +
217- ((.SourceMetadata? .Data? .Filesystem? .line // .SourceMetadata? .Data? .Git? .line) | tostring) +
261+ (((try .SourceMetadata.Data.Filesystem.line catch null) // (try .SourceMetadata.Data.Git.line catch null) ) | tostring) +
218262 "` → `" +
219263 (if (.Raw | length) > 8 then (.Raw[:4] + "***" + .Raw[-4:]) else "***" end) +
220264 "`"' results.json 2>/dev/null || echo "- Error processing results")
@@ -261,23 +305,28 @@ jobs:
261305 - name : Create scan report
262306 env :
263307 GITHUB_REF_NAME : ${{ github.ref_name }}
308+ GH_REPOSITORY : ${{ github.repository }}
309+ GH_SHA : ${{ github.sha }}
310+ TOTAL_SECRETS : ${{ steps.scan.outputs.total }}
311+ VERIFIED_SECRETS : ${{ steps.scan.outputs.verified }}
312+ UNVERIFIED_SECRETS : ${{ steps.scan.outputs.unverified }}
264313 run : |
265314 {
266315 echo "TruffleHog Scan Report"
267316 echo "====================="
268317 echo "Date: $(date)"
269- echo "Repository: ${{ github.repository } }"
318+ echo "Repository: ${GH_REPOSITORY }"
270319 echo "Branch: ${GITHUB_REF_NAME}"
271- echo "Commit: ${{ github.sha } }"
320+ echo "Commit: ${GH_SHA }"
272321 echo ""
273322 echo "Summary:"
274- echo "- Total secrets: ${{ steps.scan.outputs.total } }"
275- echo "- Verified: ${{ steps.scan.outputs.verified } }"
276- echo "- Unverified: ${{ steps.scan.outputs.unverified } }"
323+ echo "- Total secrets: ${TOTAL_SECRETS }"
324+ echo "- Verified: ${VERIFIED_SECRETS }"
325+ echo "- Unverified: ${UNVERIFIED_SECRETS }"
277326 echo ""
278327 echo "Detailed Results:"
279328 if [[ -f "results.json" && -s "results.json" ]] && jq empty results.json 2>/dev/null; then
280- jq -r '.[] | "- " + (if .Verified then "VERIFIED" else "Unverified" end) + " " + .DetectorName + " at " + ((.SourceMetadata? .Data? .Filesystem? .file // .SourceMetadata? .Data? .Git? .file) // "unknown") + ":" + ((.SourceMetadata? .Data? .Filesystem? .line // .SourceMetadata? .Data? .Git? .line) | tostring) + " → " + (if (.Raw | length) > 8 then (.Raw[:4] + "***" + .Raw[-4:]) else "***" end)' results.json 2>/dev/null || echo "Error processing results"
329+ jq -r '.[] | "- " + (if .Verified then "VERIFIED" else "Unverified" end) + " " + .DetectorName + " at " + (((try .SourceMetadata.Data.Filesystem.file catch null) // (try .SourceMetadata.Data.Git.file catch null)) // "unknown") + ":" + (((try .SourceMetadata.Data.Filesystem.line catch null) // (try .SourceMetadata.Data.Git.line catch null) ) | tostring) + " → " + (if (.Raw | length) > 8 then (.Raw[:4] + "***" + .Raw[-4:]) else "***" end)' results.json 2>/dev/null || echo "Error processing results"
281330 else
282331 echo "No secrets detected"
283332 fi
0 commit comments