5050 - name : Remove persisted credentials
5151 run : git config --unset-all http.https://github.com/.extraheader
5252
53+ - name : Fetch org-wide TruffleHog exclude patterns
54+ env :
55+ GH_TOKEN : ${{ github.token }}
56+ run : |
57+ DEST=/tmp/trufflehog-exclude.txt
58+ REPO=grafana/security-github-actions
59+ FILE_PATH=trufflehog/exclude-paths.txt
60+
61+ # Resolve the ref the calling workflow used (e.g. @main, @feature/branch, @v1).
62+ CALLER_REF="main"
63+ if [[ -n "${{ github.workflow_ref }}" ]]; then
64+ CALLER_REF=$(echo "${{ github.workflow_ref }}" | sed 's|.*@||; s|^refs/heads/||; s|^refs/tags/||')
65+ fi
66+
67+ LOADED=false
68+ for REF in main "${CALLER_REF}"; do
69+ [[ "$LOADED" == "true" ]] && break
70+ if gh api "repos/${REPO}/contents/${FILE_PATH}?ref=${REF}" \
71+ -H "Accept: application/vnd.github.v3.raw" -o "${DEST}" 2>/dev/null && [[ -s "${DEST}" ]]; then
72+ echo "Loaded exclude patterns from ${REPO}@${REF} (GitHub API)"
73+ LOADED=true
74+ elif curl -fsSL "https://raw.githubusercontent.com/${REPO}/${REF}/${FILE_PATH}" \
75+ -o "${DEST}" 2>/dev/null && [[ -s "${DEST}" ]]; then
76+ echo "Loaded exclude patterns from raw.githubusercontent.com@${REF}"
77+ LOADED=true
78+ fi
79+ done
80+
81+ if [[ "$LOADED" != "true" ]]; then
82+ echo "::warning::Could not fetch TruffleHog exclude patterns from ${REPO} (tried main and ${CALLER_REF}). Scanning without exclusions."
83+ touch "${DEST}"
84+ fi
85+
86+ echo "--- effective exclude patterns ---"
87+ cat "${DEST}"
88+
5389 - name : Install TruffleHog
5490 run : |
5591 # Download binary directly from GitHub releases for supply chain security
@@ -67,53 +103,25 @@ jobs:
67103 sudo chmod +x /usr/local/bin/trufflehog
68104 trufflehog --version
69105
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-
94106 - name : Scan for secrets
95107 id : scan
96108 run : |
97109 set +e
98110 echo "[]" > results.json
99111
112+ # Extract non-comment, non-blank patterns for the shell pre-filter.
113+ grep -vE '^\s*#|^\s*$' /tmp/trufflehog-exclude.txt > /tmp/exclude-regexes.txt 2>/dev/null || true
114+
100115 if [[ "${{ github.event_name }}" == "pull_request" ]]; then
101- # PR: Scan only changed files (using two-dot diff with explicit base SHA)
102116 echo "Scanning changed files in PR..."
103117 git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} > changed-files.txt
104118
105119 if [[ -s changed-files.txt ]]; then
106120 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
121+ if [[ -s /tmp/exclude-regexes.txt ]] && echo "$file" | grep -qEf /tmp/exclude-regexes.txt 2>/dev/null; then
122+ echo "Skipping: ${file} (matches exclude pattern)"
123+ continue
124+ fi
117125
118126 if [[ -f "${file}" ]]; then
119127 echo "Scanning: ${file}"
@@ -124,7 +132,6 @@ jobs:
124132 echo "No files changed"
125133 fi
126134 else
127- # Push to main: Scan current filesystem
128135 echo "Scanning current filesystem..."
129136 trufflehog filesystem . --exclude-paths /tmp/trufflehog-exclude.txt --concurrency 16 --json --no-update --results=verified,unverified > results.ndjson || true
130137 fi
@@ -141,8 +148,12 @@ jobs:
141148 # Filter out CHANGELOG git hashes if we have results
142149 if jq -e 'length > 0' results.json >/dev/null 2>&1; then
143150 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")))
151+ (
152+ (try .SourceMetadata.Data.Filesystem.file catch null) //
153+ (try .SourceMetadata.Data.Git.file catch null) //
154+ ""
155+ ) as $file |
156+ ((($file | test("CHANGELOG|HISTORY\\.md|NEWS\\.md"; "i")) and (.Raw | test("^[0-9a-f]{7,40}$"; "i"))) | not)
146157 )]' results.json > results.json.tmp && mv results.json.tmp results.json
147158 fi
148159 else
@@ -212,9 +223,9 @@ jobs:
212223 "- " +
213224 (if .Verified then "**VERIFIED SECRET**" else "**Possible secret**" end) +
214225 " (" + .DetectorName + ") at `" +
215- ((.SourceMetadata? .Data? .Filesystem? .file // .SourceMetadata? .Data? .Git? .file) // "unknown") +
226+ (((try .SourceMetadata.Data.Filesystem.file catch null) // (try .SourceMetadata.Data.Git.file catch null) ) // "unknown") +
216227 ":" +
217- ((.SourceMetadata? .Data? .Filesystem? .line // .SourceMetadata? .Data? .Git? .line) | tostring) +
228+ (((try .SourceMetadata.Data.Filesystem.line catch null) // (try .SourceMetadata.Data.Git.line catch null) ) | tostring) +
218229 "` → `" +
219230 (if (.Raw | length) > 8 then (.Raw[:4] + "***" + .Raw[-4:]) else "***" end) +
220231 "`"' results.json 2>/dev/null || echo "- Error processing results")
@@ -277,7 +288,7 @@ jobs:
277288 echo ""
278289 echo "Detailed Results:"
279290 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"
291+ 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"
281292 else
282293 echo "No secrets detected"
283294 fi
0 commit comments