Skip to content

Commit 98799fe

Browse files
Merge pull request #73 from grafana/reusable-trufflehog-workflow
Add reusable TruffleHog secret scanning workflow
2 parents 5c111b2 + e1e3f95 commit 98799fe

File tree

5 files changed

+259
-2
lines changed

5 files changed

+259
-2
lines changed

.DS_Store

6 KB
Binary file not shown.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: TruffleHog Secret Scanning
2+
3+
# This workflow is designed to be enforced org-wide via GitHub rulesets
4+
# It calls the reusable TruffleHog workflow with sensible defaults for org-wide deployment
5+
6+
on:
7+
pull_request:
8+
types: [opened, synchronize, reopened]
9+
push:
10+
branches:
11+
- main
12+
13+
permissions:
14+
contents: read
15+
pull-requests: write
16+
checks: write
17+
18+
jobs:
19+
secret-scan:
20+
name: TruffleHog Secret Scan
21+
uses: ./.github/workflows/reusable-trufflehog.yml
22+
with:
23+
# Simplified workflow - only what you need
24+
fail-on-verified: "true" # Always fail on real secrets
25+
fail-on-unverified: "false" # Lenient for org-wide adoption
26+
runs-on: ${{ !github.event.repository.private && 'ubuntu-latest' || 'ubuntu-x86-large' }} # Large runner for internal repositories
27+
secrets: inherit
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: Reusable TruffleHog Secret Scan
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
fail-on-verified:
7+
description: "Fail workflow on verified secrets"
8+
required: false
9+
default: "true"
10+
type: string
11+
fail-on-unverified:
12+
description: "Fail workflow on unverified secrets"
13+
required: false
14+
default: "false"
15+
type: string
16+
runs-on:
17+
description: "The runner to use for the job"
18+
required: false
19+
default: "ubuntu-latest"
20+
type: string
21+
22+
permissions:
23+
contents: read
24+
pull-requests: write
25+
26+
env:
27+
# renovate: datasource=github-releases depName=trufflesecurity/trufflehog
28+
TRUFFLEHOG_VERSION: 3.89.2
29+
30+
jobs:
31+
trufflehog-scan:
32+
runs-on: ${{ inputs.runs-on }}
33+
34+
steps:
35+
- name: Checkout repository
36+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
37+
with:
38+
fetch-depth: 100
39+
persist-credentials: false
40+
41+
- name: Install TruffleHog
42+
run: |
43+
# Download binary directly from GitHub releases for supply chain security
44+
VERSION="v${{ env.TRUFFLEHOG_VERSION }}"
45+
ARCH="linux_amd64"
46+
BINARY_URL="https://github.com/trufflesecurity/trufflehog/releases/download/${VERSION}/trufflehog_${VERSION#v}_${ARCH}.tar.gz"
47+
48+
curl -sSfL "${BINARY_URL}" | tar -xz -C /tmp
49+
sudo mv /tmp/trufflehog /usr/local/bin/trufflehog
50+
sudo chmod +x /usr/local/bin/trufflehog
51+
trufflehog --version
52+
53+
- name: Scan for secrets
54+
id: scan
55+
run: |
56+
set +e
57+
echo "[]" > results.json
58+
59+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
60+
# PR: Scan only changed files
61+
echo "Scanning changed files in PR..."
62+
git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.sha }} > changed-files.txt
63+
64+
if [[ -s changed-files.txt ]]; then
65+
while IFS= read -r file; do
66+
if [[ -f "${file}" ]]; then
67+
echo "Scanning: ${file}"
68+
trufflehog filesystem "${file}" --json --no-update --results=verified,unverified >> results.ndjson || true
69+
fi
70+
done < changed-files.txt
71+
else
72+
echo "No files changed"
73+
fi
74+
else
75+
# Push to main: Scan current filesystem
76+
echo "Scanning current filesystem..."
77+
trufflehog filesystem . --json --no-update --results=verified,unverified > results.ndjson || true
78+
fi
79+
80+
# Process results
81+
if [[ -s results.ndjson ]]; then
82+
grep -v '^$' results.ndjson | jq -s '.' > results.json 2>/dev/null || echo "[]" > results.json
83+
fi
84+
85+
# Count secrets
86+
if jq empty results.json 2>/dev/null; then
87+
VERIFIED=$(jq '[.[] | select(.Verified==true)] | length' results.json 2>/dev/null || echo "0")
88+
UNVERIFIED=$(jq '[.[] | select(.Verified==false)] | length' results.json 2>/dev/null || echo "0")
89+
else
90+
VERIFIED=0
91+
UNVERIFIED=0
92+
echo "[]" > results.json
93+
fi
94+
95+
TOTAL=$((VERIFIED+UNVERIFIED))
96+
echo "Found ${TOTAL} potential secrets (${VERIFIED} verified, ${UNVERIFIED} unverified)"
97+
98+
echo "verified=${VERIFIED}" >> "${GITHUB_OUTPUT}"
99+
echo "unverified=${UNVERIFIED}" >> "${GITHUB_OUTPUT}"
100+
echo "total=${TOTAL}" >> "${GITHUB_OUTPUT}"
101+
102+
- name: Hide previous TruffleHog comments
103+
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
104+
uses: int128/hide-comment-action@a30d551065e4231e6d7a671bb5ce884f9ee6417b # v1.43.0
105+
with:
106+
ends-with: "<!-- trufflehog-secret-scan-comment -->"
107+
108+
- name: Generate PR comment
109+
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
110+
id: comment-body
111+
run: |
112+
VERIFIED=${{ steps.scan.outputs.verified }}
113+
UNVERIFIED=${{ steps.scan.outputs.unverified }}
114+
TOTAL=$((VERIFIED+UNVERIFIED))
115+
116+
if [[ $TOTAL -eq 0 ]]; then
117+
{
118+
echo 'body<<EOF'
119+
echo '**TruffleHog Scan Results**'
120+
echo ''
121+
echo 'No secrets detected in this PR.'
122+
echo 'EOF'
123+
} >> "$GITHUB_OUTPUT"
124+
else
125+
# Generate findings list
126+
FINDINGS=$(jq -r '.[] |
127+
"- " +
128+
(if .Verified then "**VERIFIED SECRET**" else "**Possible secret**" end) +
129+
" (" + .DetectorName + ") at `" +
130+
((.SourceMetadata?.Data?.Filesystem?.file // .SourceMetadata?.Data?.Git?.file) // "unknown") +
131+
":" +
132+
((.SourceMetadata?.Data?.Filesystem?.line // .SourceMetadata?.Data?.Git?.line) | tostring) +
133+
"` → `" +
134+
(if (.Raw | length) > 8 then (.Raw[:4] + "***" + .Raw[-4:]) else "***" end) +
135+
"`"' results.json 2>/dev/null || echo "- Error processing results")
136+
137+
ACTION_TEXT=""
138+
if [[ $VERIFIED -gt 0 ]]; then
139+
ACTION_TEXT="**ACTION REQUIRED:** Rotate verified credentials immediately."
140+
else
141+
ACTION_TEXT="**Review:** Check if unverified secrets are false positives."
142+
fi
143+
144+
{
145+
echo 'body<<EOF'
146+
echo "**TruffleHog Scan Results**"
147+
echo ''
148+
echo "**Summary:** Found ${TOTAL} potential secrets (${VERIFIED} verified, ${UNVERIFIED} unverified)"
149+
echo ''
150+
echo "${FINDINGS}"
151+
echo ''
152+
echo "${ACTION_TEXT}"
153+
echo 'EOF'
154+
} >> "$GITHUB_OUTPUT"
155+
fi
156+
157+
- name: Post PR comment
158+
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
159+
uses: int128/comment-action@f4faf53666ef83da7d274fa2007e9212c4d719c3 # v1.39.0
160+
with:
161+
post: |
162+
${{ steps.comment-body.outputs.body }}
163+
<!-- trufflehog-secret-scan-comment -->
164+
165+
- name: Create scan report
166+
env:
167+
GITHUB_REF_NAME: ${{ github.ref_name }}
168+
run: |
169+
{
170+
echo "TruffleHog Scan Report"
171+
echo "====================="
172+
echo "Date: $(date)"
173+
echo "Repository: ${{ github.repository }}"
174+
echo "Branch: ${GITHUB_REF_NAME}"
175+
echo "Commit: ${{ github.sha }}"
176+
echo ""
177+
echo "Summary:"
178+
echo "- Total secrets: ${{ steps.scan.outputs.total }}"
179+
echo "- Verified: ${{ steps.scan.outputs.verified }}"
180+
echo "- Unverified: ${{ steps.scan.outputs.unverified }}"
181+
echo ""
182+
echo "Detailed Results:"
183+
if [[ -f "results.json" && -s "results.json" ]] && jq empty results.json 2>/dev/null; then
184+
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"
185+
else
186+
echo "No secrets detected"
187+
fi
188+
} > trufflehog_scan.txt
189+
190+
- name: Upload scan results
191+
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
192+
if: always()
193+
with:
194+
name: trufflehog_scan
195+
path: trufflehog_scan.txt
196+
retention-days: 30
197+
198+
- name: Check failure policy
199+
env:
200+
FAIL_ON_VERIFIED: ${{ inputs.fail-on-verified }}
201+
FAIL_ON_UNVERIFIED: ${{ inputs.fail-on-unverified }}
202+
VERIFIED_COUNT: ${{ steps.scan.outputs.verified }}
203+
UNVERIFIED_COUNT: ${{ steps.scan.outputs.unverified }}
204+
run: |
205+
SHOULD_FAIL=false
206+
if [[ "${FAIL_ON_VERIFIED}" == "true" && "${VERIFIED_COUNT}" != "0" ]]; then
207+
SHOULD_FAIL=true
208+
fi
209+
if [[ "${FAIL_ON_UNVERIFIED}" == "true" && "${UNVERIFIED_COUNT}" != "0" ]]; then
210+
SHOULD_FAIL=true
211+
fi
212+
213+
if [[ "${SHOULD_FAIL}" == "true" ]]; then
214+
echo "Workflow failed due to secrets found (verified: ${VERIFIED_COUNT}, unverified: ${UNVERIFIED_COUNT})"
215+
exit 1
216+
else
217+
echo "No action needed - secrets within configured thresholds"
218+
fi

pre-commit/trufflehog.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,4 @@ docker \
1616
file:///workdir \
1717
--since-commit HEAD \
1818
--results=verified,unknown \
19-
--log-level=-1 \
20-
--fail
19+
--fail

renovate.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,18 @@
22
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
33
"extends": [
44
"config:recommended"
5+
],
6+
"customManagers": [
7+
{
8+
"customType": "regex",
9+
"fileMatch": [
10+
"(?:^|/)\\.github/(?:workflows|actions)/.+\\.ya?ml$",
11+
"(?:^|/)action\\.ya?ml$",
12+
"(?:^|/)pre-commit/.+\\.sh$"
13+
],
14+
"matchStrings": [
15+
"# renovate: datasource=(?<datasource>[a-z-.]+?) depName=(?<depName>[^\\s]+?)(?: (?:lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?\\s+[A-Za-z0-9_-]+[_-](?:VERSION|version)\\s*[:=]\\s*[\"']?(?<currentValue>[^\"'@\\n]+)(?:@(?<currentDigest>sha256:[a-f0-9]+))?[\"']?"
16+
]
17+
}
518
]
619
}

0 commit comments

Comments
 (0)