Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
43162b1
Add TruffleHog secret scanning workflow and docs
GAdityaVarma Jan 7, 2026
200cee2
Update README.md
GAdityaVarma Jan 7, 2026
bf7b85b
Update trufflehog-scan.yml
GAdityaVarma Jan 7, 2026
527f3f2
Update trufflehog-scan.yml
GAdityaVarma Jan 7, 2026
599658f
Update trufflehog-scan.yml
GAdityaVarma Jan 7, 2026
f42179b
Enhance TruffleHog workflow with PR comments and commit status
GAdityaVarma Jan 7, 2026
e496bac
Update trufflehog_readme.md
GAdityaVarma Jan 7, 2026
d279b66
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
1aae01b
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
77909eb
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
c120b09
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
174b7d0
Update TruffleHog workflow to handle resolved secrets
GAdityaVarma Jan 8, 2026
7ded023
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
40260d7
Update trufflehog-scan.yml
GAdityaVarma Jan 8, 2026
5907188
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
c38506c
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
6f0f819
Remove workflow_dispatch trigger for ruleset compatibility
GAdityaVarma Jan 9, 2026
2a55bbb
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
e1358eb
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
f30fed0
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
3f65074
Improve TruffleHog scan workflow and update docs
GAdityaVarma Jan 9, 2026
467594b
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
693b2e7
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
8770dab
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
1a61036
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
aa00512
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
d57e46b
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
0bff560
Update trufflehog-scan.yml
GAdityaVarma Jan 9, 2026
cbfc2c8
Update trufflehog_readme.md
GAdityaVarma Jan 9, 2026
f610f23
Update trufflehog_readme.md
GAdityaVarma Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions .github/workflows/trufflehog-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
name: TruffleHog Secret Scan

on:
pull_request_target:
types: [opened, synchronize, reopened]
workflow_dispatch:

permissions:
contents: read
pull-requests: write

# Default exclusion patterns (regex format)
# Supports: exact filenames, wildcards, regex patterns
# Examples:
# Exact file: ^config/settings\.json$
# Directory: ^node_modules/
# Extension: \.lock$
# Wildcard: .*\.min\.js$
# Regex: ^src/test/.*_test\.py$

env:
DEFAULT_EXCLUDES: |
^node_modules/
^vendor/
^\.git/
\.lock$
^package-lock\.json$
^yarn\.lock$
^pnpm-lock\.yaml$
\.min\.js$
\.min\.css$

jobs:
trufflehog-scan:
name: Scan PR for Secrets
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Fetch PR head commits
if: github.event_name != 'workflow_dispatch'
run: |
# Fetch PR commits using GitHub's merge ref (works for all PRs including forks)
git fetch origin +refs/pull/${{ github.event.pull_request.number }}/head:refs/remotes/origin/pr-head
echo "Fetched PR #${{ github.event.pull_request.number }} head commit: ${{ github.event.pull_request.head.sha }}"

- name: Setup exclude config
id: config
run: |
# Always include default exclusions
echo "Adding default exclusions"
cat << 'EOF' > .trufflehog-ignore
${{ env.DEFAULT_EXCLUDES }}
EOF

# Append repo/org-level custom exclusions if defined
if [ -n "${{ vars.TRUFFLEHOG_EXCLUDES }}" ]; then
echo "Adding repo/org-level TRUFFLEHOG_EXCLUDES patterns"
# Support both comma-separated and newline-separated patterns
echo "${{ vars.TRUFFLEHOG_EXCLUDES }}" | tr ',' '\n' | sed '/^$/d' >> .trufflehog-ignore
fi

echo "Exclusion patterns:"
cat .trufflehog-ignore
echo "exclude_args=--exclude-paths=.trufflehog-ignore" >> $GITHUB_OUTPUT

- name: TruffleHog Scan
id: trufflehog
uses: trufflesecurity/trufflehog@main
continue-on-error: true
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
extra_args: --json ${{ steps.config.outputs.exclude_args }}

- name: Parse scan results
id: parse
if: github.event_name != 'workflow_dispatch'
run: |
# Capture TruffleHog JSON output by re-running with same args
echo "Parsing TruffleHog results..."

VERIFIED_COUNT=0
UNVERIFIED_COUNT=0

SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:latest \
git file:///tmp/ \
--since-commit ${{ github.event.pull_request.base.sha }} \
--branch ${{ github.event.pull_request.head.sha }} \
--json \
${{ steps.config.outputs.exclude_args }} \
--no-update 2>/dev/null || true)

# Parse JSON lines and create GitHub annotations
if [ -n "$SCAN_OUTPUT" ]; then
while IFS= read -r line; do
# Skip non-JSON lines (info logs)
if ! echo "$line" | jq -e '.DetectorName' > /dev/null 2>&1; then
continue
fi

FILE=$(echo "$line" | jq -r '.SourceMetadata.Data.Git.file // "unknown"')
LINE_NUM=$(echo "$line" | jq -r '.SourceMetadata.Data.Git.line // 1')
DETECTOR=$(echo "$line" | jq -r '.DetectorName // "Secret"')
VERIFIED=$(echo "$line" | jq -r '.Verified // false')

if [ "$VERIFIED" == "true" ]; then
VERIFIED_COUNT=$((VERIFIED_COUNT + 1))
# Error annotation for verified secrets
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!"
else
UNVERIFIED_COUNT=$((UNVERIFIED_COUNT + 1))
# Warning annotation for unverified secrets
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."
fi
done <<< "$SCAN_OUTPUT"
fi

echo "verified_count=${VERIFIED_COUNT}" >> $GITHUB_OUTPUT
echo "unverified_count=${UNVERIFIED_COUNT}" >> $GITHUB_OUTPUT
echo "Scan complete: ${VERIFIED_COUNT} verified, ${UNVERIFIED_COUNT} unverified secrets found"

- name: Process scan results
id: process
if: github.event_name != 'workflow_dispatch'
run: |
VERIFIED=${{ steps.parse.outputs.verified_count || 0 }}
UNVERIFIED=${{ steps.parse.outputs.unverified_count || 0 }}

if [ "$VERIFIED" -gt 0 ]; then
# Verified secrets found - must fail
echo "has_verified=true" >> $GITHUB_OUTPUT
echo "has_secrets=true" >> $GITHUB_OUTPUT
echo "description=Found ${VERIFIED} verified (active) secrets - action required" >> $GITHUB_OUTPUT
elif [ "$UNVERIFIED" -gt 0 ]; then
# Only unverified secrets - warn but pass
echo "has_verified=false" >> $GITHUB_OUTPUT
echo "has_secrets=true" >> $GITHUB_OUTPUT
echo "description=Found ${UNVERIFIED} unverified potential secrets - review recommended" >> $GITHUB_OUTPUT
else
# No secrets
echo "has_verified=false" >> $GITHUB_OUTPUT
echo "has_secrets=false" >> $GITHUB_OUTPUT
echo "description=No secrets detected in PR changes" >> $GITHUB_OUTPUT
fi

- name: Post PR comment on findings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need a comment? Can't we just rely on annotations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why PR Comments are better for security findings:

  • Immediate visibility - Developers see the alert without extra navigation
  • Actionable - The comment includes remediation steps right there
  • Email notification - PR comment triggers email, ensuring developer sees it
  • Historical record - Comment stays in PR history for auditing
  • Team visibility - Reviewers also see the security issue immediately
  • Mobile-friendly - Easy to check on phone

if: github.event_name != 'workflow_dispatch'
uses: actions/github-script@v7
with:
script: |
const commentMarker = '<!-- TRUFFLEHOG-SCAN-COMMENT -->';
const commitSha = '${{ github.event.pull_request.head.sha }}';
const shortSha = commitSha.substring(0, 7);
const hasSecrets = '${{ steps.process.outputs.has_secrets }}' === 'true';
const hasVerified = '${{ steps.process.outputs.has_verified }}' === 'true';
const verifiedCount = '${{ steps.parse.outputs.verified_count }}' || '0';
const unverifiedCount = '${{ steps.parse.outputs.unverified_count }}' || '0';

// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
per_page: 100
});

const existing = comments.find(c => c.body && c.body.includes(commentMarker));

let body;
if (!hasSecrets) {
// No secrets found
if (existing) {
// Check if existing comment was a critical/blocking one (had verified secrets)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the PR comment in case developer removes unverified secrets in subsequent commit?

const wasBlocking = existing.body.includes('CRITICAL') || existing.body.includes(':rotating_light:');
if (wasBlocking) {
// Update to show verified secrets are now resolved
body = `${commentMarker}
## :white_check_mark: Secret Scanning Passed

**No secrets detected in this pull request.**

**Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))

Previous issues have been resolved. Thank you for addressing the security concerns!

---
*This comment will be updated if new secrets are detected in future commits.*
`;
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
}
// If it was just a warning (unverified only), leave it as-is
}
// If no existing comment and no secrets, don't post anything
return;
}

// Secrets found - create or update warning comment
let severity, icon, action;
if (hasVerified) {
severity = 'CRITICAL';
icon = ':rotating_light:';
action = 'This PR is **blocked** until verified secrets are removed.';
} else {
severity = 'Warning';
icon = ':warning:';
action = 'This PR can proceed, but please review the potential secrets below.';
}

body = `${commentMarker}
## ${icon} Secret Scanning ${severity}

**TruffleHog scan results:**
- **Verified (active) secrets:** ${verifiedCount} ${verifiedCount > 0 ? ':x:' : ':white_check_mark:'}
- **Unverified (potential) secrets:** ${unverifiedCount} ${unverifiedCount > 0 ? ':warning:' : ':white_check_mark:'}

**Scanned commit:** \`${shortSha}\` ([${commitSha}](${{ github.server_url }}/${{ github.repository }}/commit/${commitSha}))

${action}

### What to do:
1. **Review the workflow annotations** - they point to exact file and line locations
2. **Remove any exposed secrets** from your code
3. **Rotate compromised credentials** - especially verified ones
4. **Push the fix** to this branch

### Understanding Results
| Type | Meaning | Action Required |
|------|---------|-----------------|
| **Verified** | Confirmed active credential | **Must remove & rotate** - PR blocked |
| **Unverified** | Potential secret pattern | Review recommended - PR can proceed |

Check the [workflow run logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.

---
*Verified secrets are confirmed active by TruffleHog. Unverified secrets match known patterns but couldn't be validated.*
`;

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: body
});
}

- name: Fail workflow if verified secrets found
if: steps.process.outputs.has_verified == 'true'
run: |
echo "::error::VERIFIED SECRETS DETECTED - These are confirmed active credentials that must be removed and rotated immediately."
exit 1
Loading