@@ -9,6 +9,15 @@ permissions:
99 contents : read
1010 pull-requests : write
1111
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+
1221env :
1322 DEFAULT_EXCLUDES : |
1423 ^node_modules/
@@ -30,19 +39,28 @@ jobs:
3039 - name : Checkout repository
3140 uses : actions/checkout@v4
3241 with :
33- ref : ${{ github.event.pull_request.head.sha }}
34- fetch-depth : 2
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 }}"
3550
3651 - name : Setup exclude config
3752 id : config
3853 run : |
54+ # Always include default exclusions
3955 echo "Adding default exclusions"
4056 cat << 'EOF' > .trufflehog-ignore
4157 ${{ env.DEFAULT_EXCLUDES }}
4258 EOF
4359
60+ # Append repo/org-level custom exclusions if defined
4461 if [ -n "${{ vars.TRUFFLEHOG_EXCLUDES }}" ]; then
4562 echo "Adding repo/org-level TRUFFLEHOG_EXCLUDES patterns"
63+ # Support both comma-separated and newline-separated patterns
4664 echo "${{ vars.TRUFFLEHOG_EXCLUDES }}" | tr ',' '\n' | sed '/^$/d' >> .trufflehog-ignore
4765 fi
4866
@@ -55,62 +73,49 @@ jobs:
5573 uses : trufflesecurity/trufflehog@main
5674 continue-on-error : true
5775 with :
58- base : ${{ github.event.pull_request.head .sha }}~1
76+ base : ${{ github.event.pull_request.base .sha }}
5977 head : ${{ github.event.pull_request.head.sha }}
6078 extra_args : --json ${{ steps.config.outputs.exclude_args }}
6179
6280 - name : Parse scan results
6381 id : parse
6482 if : github.event_name != 'workflow_dispatch'
6583 run : |
84+ # Capture TruffleHog JSON output by re-running with same args
6685 echo "Parsing TruffleHog results..."
6786
6887 VERIFIED_COUNT=0
6988 UNVERIFIED_COUNT=0
7089
71- # Get changed files list
72- CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
73- echo "Changed files:"
74- echo "$CHANGED_FILES"
75-
76- # Scan only HEAD commit (current state), not history
7790 SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
78- -e GIT_CONFIG_COUNT=2 \
79- -e GIT_CONFIG_KEY_0=diff.renames \
80- -e GIT_CONFIG_VALUE_0=false \
81- -e GIT_CONFIG_KEY_1=diff.renameLimit \
82- -e GIT_CONFIG_VALUE_1=0 \
8391 ghcr.io/trufflesecurity/trufflehog:latest \
8492 git file:///tmp/ \
85- --since-commit ${{ github.event.pull_request.head .sha }}~1 \
93+ --since-commit ${{ github.event.pull_request.base .sha }} \
8694 --branch ${{ github.event.pull_request.head.sha }} \
87- --max-depth=1 \
8895 --json \
8996 ${{ steps.config.outputs.exclude_args }} \
9097 --no-update 2>/dev/null || true)
9198
99+ # Parse JSON lines and create GitHub annotations
92100 if [ -n "$SCAN_OUTPUT" ]; then
93101 while IFS= read -r line; do
102+ # Skip non-JSON lines (info logs)
94103 if ! echo "$line" | jq -e '.DetectorName' > /dev/null 2>&1; then
95104 continue
96105 fi
97106
98107 FILE=$(echo "$line" | jq -r '.SourceMetadata.Data.Git.file // "unknown"')
99-
100- # Only report if file is in the changed files list
101- if ! echo "$CHANGED_FILES" | grep -qxF "$FILE"; then
102- continue
103- fi
104-
105108 LINE_NUM=$(echo "$line" | jq -r '.SourceMetadata.Data.Git.line // 1')
106109 DETECTOR=$(echo "$line" | jq -r '.DetectorName // "Secret"')
107110 VERIFIED=$(echo "$line" | jq -r '.Verified // false')
108111
109112 if [ "$VERIFIED" == "true" ]; then
110113 VERIFIED_COUNT=$((VERIFIED_COUNT + 1))
111- echo "::error file=${FILE},line=${LINE_NUM},title=${DETECTOR} [VERIFIED]::VERIFIED ACTIVE CREDENTIAL: ${DETECTOR} found in ${FILE} at line ${LINE_NUM}. Remove and rotate immediately!"
114+ # Error annotation for verified secrets
115+ 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!"
112116 else
113117 UNVERIFIED_COUNT=$((UNVERIFIED_COUNT + 1))
118+ # Warning annotation for unverified secrets
114119 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."
115120 fi
116121 done <<< "$SCAN_OUTPUT"
@@ -128,14 +133,17 @@ jobs:
128133 UNVERIFIED=${{ steps.parse.outputs.unverified_count || 0 }}
129134
130135 if [ "$VERIFIED" -gt 0 ]; then
136+ # Verified secrets found - must fail
131137 echo "has_verified=true" >> $GITHUB_OUTPUT
132138 echo "has_secrets=true" >> $GITHUB_OUTPUT
133139 echo "description=Found ${VERIFIED} verified (active) secrets - action required" >> $GITHUB_OUTPUT
134140 elif [ "$UNVERIFIED" -gt 0 ]; then
141+ # Only unverified secrets - warn but pass
135142 echo "has_verified=false" >> $GITHUB_OUTPUT
136143 echo "has_secrets=true" >> $GITHUB_OUTPUT
137144 echo "description=Found ${UNVERIFIED} unverified potential secrets - review recommended" >> $GITHUB_OUTPUT
138145 else
146+ # No secrets
139147 echo "has_verified=false" >> $GITHUB_OUTPUT
140148 echo "has_secrets=false" >> $GITHUB_OUTPUT
141149 echo "description=No secrets detected in PR changes" >> $GITHUB_OUTPUT
@@ -154,6 +162,7 @@ jobs:
154162 const verifiedCount = '${{ steps.parse.outputs.verified_count }}' || '0';
155163 const unverifiedCount = '${{ steps.parse.outputs.unverified_count }}' || '0';
156164
165+ // Find existing comment
157166 const { data: comments } = await github.rest.issues.listComments({
158167 owner: context.repo.owner,
159168 repo: context.repo.repo,
@@ -165,26 +174,38 @@ jobs:
165174
166175 let body;
167176 if (!hasSecrets) {
177+ // No secrets found
168178 if (existing) {
169- // Update to show secrets are now resolved (whether verified or unverified)
170- body = `${commentMarker}
179+ // Check if existing comment was a critical/blocking one (had verified secrets)
180+ const wasBlocking = existing.body.includes('CRITICAL') || existing.body.includes(':rotating_light:');
181+ if (wasBlocking) {
182+ // Update to show verified secrets are now resolved
183+ body = `${commentMarker}
171184 ## :white_check_mark: Secret Scanning Passed
185+
172186 **No secrets detected in this pull request.**
187+
173188 **Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))
189+
174190 Previous issues have been resolved. Thank you for addressing the security concerns!
191+
175192 ---
176193 *This comment will be updated if new secrets are detected in future commits.*
177194 `;
178- await github.rest.issues.updateComment({
179- owner: context.repo.owner,
180- repo: context.repo.repo,
181- comment_id: existing.id,
182- body: body
183- });
195+ await github.rest.issues.updateComment({
196+ owner: context.repo.owner,
197+ repo: context.repo.repo,
198+ comment_id: existing.id,
199+ body: body
200+ });
201+ }
202+ // If it was just a warning (unverified only), leave it as-is
184203 }
204+ // If no existing comment and no secrets, don't post anything
185205 return;
186206 }
187207
208+ // Secrets found - create or update warning comment
188209 let severity, icon, action;
189210 if (hasVerified) {
190211 severity = 'CRITICAL';
@@ -198,22 +219,29 @@ jobs:
198219
199220 body = `${commentMarker}
200221 ## ${icon} Secret Scanning ${severity}
222+
201223 **TruffleHog scan results:**
202224 - **Verified (active) secrets:** ${verifiedCount} ${verifiedCount > 0 ? ':x:' : ':white_check_mark:'}
203225 - **Unverified (potential) secrets:** ${unverifiedCount} ${unverifiedCount > 0 ? ':warning:' : ':white_check_mark:'}
226+
204227 **Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))
228+
205229 ${action}
230+
206231 ### What to do:
207232 1. **Review the workflow annotations** - they point to exact file and line locations
208233 2. **Remove any exposed secrets** from your code
209234 3. **Rotate compromised credentials** - especially verified ones
210235 4. **Push the fix** to this branch
236+
211237 ### Understanding Results
212238 | Type | Meaning | Action Required |
213239 |------|---------|-----------------|
214240 | **Verified** | Confirmed active credential | **Must remove & rotate** - PR blocked |
215241 | **Unverified** | Potential secret pattern | Review recommended - PR can proceed |
242+
216243 Check the [workflow run logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
244+
217245 ---
218246 *Verified secrets are confirmed active by TruffleHog. Unverified secrets match known patterns but couldn't be validated.*
219247 `;
0 commit comments