Skip to content

Commit 02c701e

Browse files
GAdityaVarmaCopilotbrijeshp56
authored
PDP-684 Add TruffleHog secret scanning workflow for PR validation (#16)
* Add TruffleHog secret scanning workflow and docs Introduces a centralized GitHub Actions workflow for scanning pull requests for secrets using TruffleHog. Includes a detailed README with setup instructions, exclusion pattern configuration, override options, and troubleshooting guidance. * Update README.md * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Enhance TruffleHog workflow with PR comments and commit status The workflow now posts PR comments with secret scan findings, sets commit status to pass/fail, and provides clearer merge blocking. Documentation was updated and renamed to trufflehog_readme.md to reflect new features, including secret classification and improved fork PR support. * Update trufflehog_readme.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update TruffleHog workflow to handle resolved secrets Adds a workflow step to update the PR comment when previously detected secrets are resolved, marking the PR as clear. Updates documentation to clarify that exclusion patterns are additive, describes the new comment update behavior, and improves the remediation and PR comment sections for clarity. * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Remove workflow_dispatch trigger for ruleset compatibility * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Improve TruffleHog scan workflow and update docs Enhances the TruffleHog GitHub Actions workflow to better distinguish between scan errors and actual secret findings, adding a verification step for failed scans. Updates documentation to clarify exclusion pattern behavior, workflow triggers, and runtime logic for more accurate and secure secret scanning. * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog-scan.yml * Update trufflehog_readme.md * Update trufflehog_readme.md * PDP-684: updated the workflow for updating the pullrequest comment * PDP-684: Updated to update the comment * PDP-684: Update the existing pull request comment if secrets are resolved. (#18) * PDP-684: updated the workflow for updating the pullrequest comment * PDP-684: Updated to update the comment * PDP-684: Updated workflow to make sure to scan renamed files * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml for detecting the renamed files PDP-684 : Update trufflehog-scan.yml for detecting the renamed files * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684 : Update trufflehog-scan.yml PDP-684 : Update trufflehog-scan.yml * PDP-684: updated workflow to checkout only head commit * PDP-684: updated workflow to checkout only head commit1 * PDP-684 : Reverting my changes for trufflehog * Update trufflehog-scan.yml * Improve secret scan comment update logic Refines the logic for updating PR comments after secret scanning. Now checks if the 'Passed' state is already present before updating, and determines the type of previously found secrets for more accurate messaging. * Update trufflehog-scan.yml * Update trufflehog-scan.yml --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Brijesh Kumar Patel <BrijeshKumar.Patel@progress.com> Co-authored-by: brijeshp56 <brpatel@progress.com>
1 parent b5019ed commit 02c701e

File tree

2 files changed

+664
-0
lines changed

2 files changed

+664
-0
lines changed
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
name: TruffleHog Secret Scan
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, synchronize, reopened]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
12+
# Default exclusion patterns (regex format)
13+
# Supports: exact filenames, wildcards, regex patterns
14+
# Examples:
15+
# Exact file: ^config/settings\.json$
16+
# Directory: ^node_modules/
17+
# Extension: \.lock$
18+
# Wildcard: .*\.min\.js$
19+
# Regex: ^src/test/.*_test\.py$
20+
21+
env:
22+
DEFAULT_EXCLUDES: |
23+
^node_modules/
24+
^vendor/
25+
^\.git/
26+
\.lock$
27+
^package-lock\.json$
28+
^yarn\.lock$
29+
^pnpm-lock\.yaml$
30+
\.min\.js$
31+
\.min\.css$
32+
33+
jobs:
34+
trufflehog-scan:
35+
name: Scan PR for Secrets
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
43+
44+
- name: Fetch PR head commits
45+
if: github.event_name != 'workflow_dispatch'
46+
run: |
47+
# Fetch PR commits using GitHub's merge ref (works for all PRs including forks)
48+
git fetch origin +refs/pull/${{ github.event.pull_request.number }}/head:refs/remotes/origin/pr-head
49+
echo "Fetched PR #${{ github.event.pull_request.number }} head commit: ${{ github.event.pull_request.head.sha }}"
50+
51+
- name: Setup exclude config
52+
id: config
53+
run: |
54+
# Always include default exclusions
55+
echo "Adding default exclusions"
56+
cat << 'EOF' > .trufflehog-ignore
57+
${{ env.DEFAULT_EXCLUDES }}
58+
EOF
59+
60+
# Append repo/org-level custom exclusions if defined
61+
if [ -n "${{ vars.TRUFFLEHOG_EXCLUDES }}" ]; then
62+
echo "Adding repo/org-level TRUFFLEHOG_EXCLUDES patterns"
63+
# Support both comma-separated and newline-separated patterns
64+
echo "${{ vars.TRUFFLEHOG_EXCLUDES }}" | tr ',' '\n' | sed '/^$/d' >> .trufflehog-ignore
65+
fi
66+
67+
echo "Exclusion patterns:"
68+
cat .trufflehog-ignore
69+
echo "exclude_args=--exclude-paths=.trufflehog-ignore" >> $GITHUB_OUTPUT
70+
71+
- name: TruffleHog Scan
72+
id: trufflehog
73+
uses: trufflesecurity/trufflehog@main
74+
continue-on-error: true
75+
with:
76+
base: ${{ github.event.pull_request.base.sha }}
77+
head: ${{ github.event.pull_request.head.sha }}
78+
extra_args: --json ${{ steps.config.outputs.exclude_args }}
79+
80+
- name: Parse scan results
81+
id: parse
82+
if: github.event_name != 'workflow_dispatch'
83+
run: |
84+
# Scan the current state of PR files (not git history)
85+
# This ensures renamed files and removed secrets are handled correctly
86+
echo "Parsing TruffleHog results..."
87+
88+
VERIFIED_COUNT=0
89+
UNVERIFIED_COUNT=0
90+
91+
# Checkout PR head to scan current file state
92+
git checkout ${{ github.event.pull_request.head.sha }} --quiet
93+
94+
# Get list of files changed in this PR (with rename detection)
95+
# -M enables rename detection, showing only the new filename for renamed files
96+
# --diff-filter=d excludes deleted files (we only want files that exist in the PR head)
97+
CHANGED_FILES=$(git diff --name-only -M --diff-filter=d ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | grep -v '^$' || true)
98+
99+
if [ -z "$CHANGED_FILES" ]; then
100+
echo "No files changed in PR"
101+
echo "verified_count=0" >> $GITHUB_OUTPUT
102+
echo "unverified_count=0" >> $GITHUB_OUTPUT
103+
exit 0
104+
fi
105+
106+
echo "Scanning changed files:"
107+
echo "$CHANGED_FILES"
108+
109+
# Scan only the changed files in their current state using filesystem scanner
110+
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
111+
ghcr.io/trufflesecurity/trufflehog:latest \
112+
filesystem /tmp/ \
113+
--json \
114+
${{ steps.config.outputs.exclude_args }} \
115+
--no-update 2>/dev/null || true)
116+
117+
# Parse JSON lines and filter to only changed files
118+
if [ -n "$SCAN_OUTPUT" ]; then
119+
while IFS= read -r line; do
120+
# Skip non-JSON lines (info logs)
121+
if ! echo "$line" | jq -e '.DetectorName' > /dev/null 2>&1; then
122+
continue
123+
fi
124+
125+
FILE=$(echo "$line" | jq -r '.SourceMetadata.Data.Filesystem.file // "unknown"')
126+
# Remove /tmp/ prefix if present
127+
FILE="${FILE#/tmp/}"
128+
129+
# Only count secrets in files that are part of this PR
130+
if ! echo "$CHANGED_FILES" | grep -qx "$FILE"; then
131+
continue
132+
fi
133+
134+
LINE_NUM=$(echo "$line" | jq -r '.SourceMetadata.Data.Filesystem.line // 1')
135+
DETECTOR=$(echo "$line" | jq -r '.DetectorName // "Secret"')
136+
VERIFIED=$(echo "$line" | jq -r '.Verified // false')
137+
138+
if [ "$VERIFIED" == "true" ]; then
139+
VERIFIED_COUNT=$((VERIFIED_COUNT + 1))
140+
# Error annotation for verified secrets
141+
echo "::error file=${FILE},line=${LINE_NUM},title=${DETECTOR} [VERIFIED]::VERIFIED ACTIVE CREDENTIAL: ${DETECTOR} found in ${FILE} at line ${LINE_NUM}. This secret is confirmed active. Remove and rotate immediately!"
142+
else
143+
UNVERIFIED_COUNT=$((UNVERIFIED_COUNT + 1))
144+
# Warning annotation for unverified secrets
145+
echo "::warning file=${FILE},line=${LINE_NUM},title=${DETECTOR} [Unverified]::Potential secret: ${DETECTOR} found in ${FILE} at line ${LINE_NUM}. Review and remove if this is a real credential."
146+
fi
147+
done <<< "$SCAN_OUTPUT"
148+
fi
149+
150+
echo "verified_count=${VERIFIED_COUNT}" >> $GITHUB_OUTPUT
151+
echo "unverified_count=${UNVERIFIED_COUNT}" >> $GITHUB_OUTPUT
152+
echo "Scan complete: ${VERIFIED_COUNT} verified, ${UNVERIFIED_COUNT} unverified secrets found"
153+
154+
- name: Process scan results
155+
id: process
156+
if: github.event_name != 'workflow_dispatch'
157+
run: |
158+
VERIFIED=${{ steps.parse.outputs.verified_count || 0 }}
159+
UNVERIFIED=${{ steps.parse.outputs.unverified_count || 0 }}
160+
161+
if [ "$VERIFIED" -gt 0 ]; then
162+
# Verified secrets found - must fail
163+
echo "has_verified=true" >> $GITHUB_OUTPUT
164+
echo "has_secrets=true" >> $GITHUB_OUTPUT
165+
echo "description=Found ${VERIFIED} verified (active) secrets - action required" >> $GITHUB_OUTPUT
166+
elif [ "$UNVERIFIED" -gt 0 ]; then
167+
# Only unverified secrets - warn but pass
168+
echo "has_verified=false" >> $GITHUB_OUTPUT
169+
echo "has_secrets=true" >> $GITHUB_OUTPUT
170+
echo "description=Found ${UNVERIFIED} unverified potential secrets - review recommended" >> $GITHUB_OUTPUT
171+
else
172+
# No secrets
173+
echo "has_verified=false" >> $GITHUB_OUTPUT
174+
echo "has_secrets=false" >> $GITHUB_OUTPUT
175+
echo "description=No secrets detected in PR changes" >> $GITHUB_OUTPUT
176+
fi
177+
178+
- name: Post PR comment on findings
179+
if: github.event_name != 'workflow_dispatch'
180+
uses: actions/github-script@v7
181+
with:
182+
script: |
183+
const commentMarker = '<!-- TRUFFLEHOG-SCAN-COMMENT -->';
184+
const commitSha = '${{ github.event.pull_request.head.sha }}';
185+
const shortSha = commitSha.substring(0, 7);
186+
const hasSecrets = '${{ steps.process.outputs.has_secrets }}' === 'true';
187+
const hasVerified = '${{ steps.process.outputs.has_verified }}' === 'true';
188+
const verifiedCount = '${{ steps.parse.outputs.verified_count }}' || '0';
189+
const unverifiedCount = '${{ steps.parse.outputs.unverified_count }}' || '0';
190+
191+
// Find existing comment
192+
const { data: comments } = await github.rest.issues.listComments({
193+
owner: context.repo.owner,
194+
repo: context.repo.repo,
195+
issue_number: context.payload.pull_request.number,
196+
per_page: 100
197+
});
198+
199+
const existing = comments.find(c => c.body && c.body.includes(commentMarker));
200+
201+
let body;
202+
if (!hasSecrets) {
203+
// No secrets found
204+
if (existing) {
205+
// Check if existing comment already shows "Passed" state
206+
const alreadyPassed = existing.body.includes(':white_check_mark: Secret Scanning Passed');
207+
208+
if (!alreadyPassed) {
209+
// Update to show all secrets are now resolved
210+
// Determine what type of secrets were previously found
211+
const hadVerified = existing.body.includes('CRITICAL') || existing.body.includes(':rotating_light:');
212+
const previousType = hadVerified ? 'verified secrets' : 'potential secrets';
213+
214+
body = `${commentMarker}
215+
## :white_check_mark: Secret Scanning Passed
216+
217+
**No secrets detected in this pull request.**
218+
219+
**Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))
220+
221+
Previous ${previousType} have been resolved. Thank you for addressing the security concerns!
222+
223+
---
224+
*This comment will be updated if new secrets are detected in future commits.*
225+
`;
226+
await github.rest.issues.updateComment({
227+
owner: context.repo.owner,
228+
repo: context.repo.repo,
229+
comment_id: existing.id,
230+
body: body
231+
});
232+
}
233+
}
234+
// If no existing comment and no secrets, don't post anything
235+
return;
236+
}
237+
238+
// Secrets found - create or update warning comment
239+
let severity, icon, action;
240+
if (hasVerified) {
241+
severity = 'CRITICAL';
242+
icon = ':rotating_light:';
243+
action = 'This PR is **blocked** until verified secrets are removed.';
244+
} else {
245+
severity = 'Warning';
246+
icon = ':warning:';
247+
action = 'This PR can proceed, but please review the potential secrets below.';
248+
}
249+
250+
body = `${commentMarker}
251+
## ${icon} Secret Scanning ${severity}
252+
253+
**TruffleHog scan results:**
254+
- **Verified (active) secrets:** ${verifiedCount} ${verifiedCount > 0 ? ':x:' : ':white_check_mark:'}
255+
- **Unverified (potential) secrets:** ${unverifiedCount} ${unverifiedCount > 0 ? ':warning:' : ':white_check_mark:'}
256+
257+
**Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))
258+
259+
${action}
260+
261+
### What to do:
262+
1. **Review the workflow annotations** - they point to exact file and line locations
263+
2. **Remove any exposed secrets** from your code
264+
3. **Rotate compromised credentials** - especially verified ones
265+
4. **Push the fix** to this branch
266+
267+
### Understanding Results
268+
| Type | Meaning | Action Required |
269+
|------|---------|-----------------|
270+
| **Verified** | Confirmed active credential | **Must remove & rotate** - PR blocked |
271+
| **Unverified** | Potential secret pattern | Review recommended - PR can proceed |
272+
273+
Check the [workflow run logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
274+
275+
---
276+
*Verified secrets are confirmed active by TruffleHog. Unverified secrets match known patterns but couldn't be validated.*
277+
`;
278+
279+
if (existing) {
280+
await github.rest.issues.updateComment({
281+
owner: context.repo.owner,
282+
repo: context.repo.repo,
283+
comment_id: existing.id,
284+
body: body
285+
});
286+
} else {
287+
await github.rest.issues.createComment({
288+
owner: context.repo.owner,
289+
repo: context.repo.repo,
290+
issue_number: context.payload.pull_request.number,
291+
body: body
292+
});
293+
}
294+
295+
- name: Fail workflow if verified secrets found
296+
if: steps.process.outputs.has_verified == 'true'
297+
run: |
298+
echo "::error::VERIFIED SECRETS DETECTED - These are confirmed active credentials that must be removed and rotated immediately."
299+
exit 1

0 commit comments

Comments
 (0)