Skip to content

Update .github/workflows/reusable-trufflehog.yml #10

Update .github/workflows/reusable-trufflehog.yml

Update .github/workflows/reusable-trufflehog.yml #10

name: Reusable TruffleHog Secret Scan

Check failure on line 1 in .github/workflows/reusable-trufflehog.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/reusable-trufflehog.yml

Invalid workflow file

(Line: 84, Col: 9): Unexpected value 'environment'
on:
workflow_call:
inputs:
fail-on-verified:
description: "Fail workflow on verified secrets"
required: false
default: "true"
type: string
fail-on-unverified:
description: "Fail workflow on unverified secrets"
required: false
default: "false"
type: string
runs-on:
description: "The runner to use for the job"
required: false
default: "ubuntu-latest"
type: string
permissions:
contents: read
pull-requests: write
env:
# renovate: datasource=github-releases depName=trufflesecurity/trufflehog
TRUFFLEHOG_VERSION: 3.89.2
jobs:
trufflehog-scan:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
persist-credentials: false
- name: Install TruffleHog
run: |
# Download binary directly from GitHub releases for supply chain security
VERSION="v${{ env.TRUFFLEHOG_VERSION }}"
# Auto-detect architecture for cross-platform support
if [[ "$(uname -m)" == "aarch64" ]]; then
ARCH="linux_arm64"
else
ARCH="linux_amd64"
fi
BINARY_URL="https://github.com/trufflesecurity/trufflehog/releases/download/${VERSION}/trufflehog_${VERSION#v}_${ARCH}.tar.gz"
curl -sSfL "${BINARY_URL}" | tar -xz -C /tmp
sudo mv /tmp/trufflehog /usr/local/bin/trufflehog
sudo chmod +x /usr/local/bin/trufflehog
trufflehog --version
- name: Create global exclusions
run: |
# Create centralized exclusion patterns for common false positives
cat > /tmp/trufflehog-exclude.txt <<'EOF'
# Lock files and checksums (contain hashes, not secrets)
path:go\.sum$
path:go\.mod$
# Dependency manifests (contain URLs that trigger false positives)
path:package\.json$
path:package-lock\.json$
path:pnpm-lock\.yaml$
path:yarn\.lock$
path:poetry\.lock$
path:Pipfile\.lock$
path:uv\.lock$
path:Cargo\.lock$
path:Gemfile\.lock$
# Grafana plugin metadata
path:grafana\.json$
EOF
echo "Created global exclusion patterns:"
cat /tmp/trufflehog-exclude.txt
- name: Scan for secrets
id: scan
environment:
BASE_REF: ${{ github.event.pull_request.base.ref }}
SHA: ${{ github.sha }}
run: |
set +e
echo "[]" > results.json
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# PR: Scan only changed files (using three-dot diff to show only PR changes)
echo "Scanning changed files in PR..."
git diff --name-only origin/${BASE_REF}...${SHA} > changed-files.txt
if [[ -s changed-files.txt ]]; then
while IFS= read -r file; do
# Get just the filename
filename=$(basename "$file")
# Skip excluded files (use case statement for cleaner matching)
case "$filename" in
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)
echo "Skipping: ${file} (excluded manifest/lock file)"
continue
;;
esac
if [[ -f "${file}" ]]; then
echo "Scanning: ${file}"
trufflehog filesystem "${file}" --exclude-paths /tmp/trufflehog-exclude.txt --concurrency 16 --json --no-update --results=verified,unverified >> results.ndjson || true
fi
done < changed-files.txt
else
echo "No files changed"
fi
else
# Push to main: Scan current filesystem
echo "Scanning current filesystem..."
trufflehog filesystem . --exclude-paths /tmp/trufflehog-exclude.txt --concurrency 16 --json --no-update --results=verified,unverified > results.ndjson || true
fi
# Process results and filter git hashes from CHANGELOG files
if [[ -s results.ndjson ]]; then
grep -v '^$' results.ndjson | jq -c 'select(
not(
((.SourceMetadata?.Data?.Filesystem?.file // .SourceMetadata?.Data?.Git?.file) // "") as $file |
($file | test("CHANGELOG|HISTORY\\.md|NEWS\\.md"; "i")) and
(.Raw | test("^[0-9a-f]{7,40}$"; "i"))
)
)' | jq -s '.' > results.json 2>/dev/null || echo "[]" > results.json
fi
# Count secrets
if jq empty results.json 2>/dev/null; then
VERIFIED=$(jq '[.[] | select(.Verified==true)] | length' results.json 2>/dev/null || echo "0")
UNVERIFIED=$(jq '[.[] | select(.Verified==false)] | length' results.json 2>/dev/null || echo "0")
else
VERIFIED=0
UNVERIFIED=0
echo "[]" > results.json
fi
TOTAL=$((VERIFIED+UNVERIFIED))
echo "Found ${TOTAL} potential secrets (${VERIFIED} verified, ${UNVERIFIED} unverified)"
echo "verified=${VERIFIED}" >> "${GITHUB_OUTPUT}"
echo "unverified=${UNVERIFIED}" >> "${GITHUB_OUTPUT}"
echo "total=${TOTAL}" >> "${GITHUB_OUTPUT}"
- name: Delete resolved comment
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.scan.outputs.total == '0' }}
run: |
# Find and delete TruffleHog comment if secrets have been resolved
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.body | contains("<!-- trufflehog-secret-scan-comment -->")) | .id' | head -1 || echo "")
if [[ -n "$COMMENT_ID" ]]; then
echo "Secrets resolved - deleting previous warning comment (ID: ${COMMENT_ID})"
gh api -X DELETE /repos/${{ github.repository }}/issues/comments/${COMMENT_ID}
echo "Comment deleted successfully"
else
echo "No existing TruffleHog comment to delete"
fi
env:
GH_TOKEN: ${{ github.token }}
- name: Generate PR comment
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.scan.outputs.total > 0 }}
id: comment-body
run: |
VERIFIED=${{ steps.scan.outputs.verified }}
UNVERIFIED=${{ steps.scan.outputs.unverified }}
TOTAL=$((VERIFIED+UNVERIFIED))
if [[ $TOTAL -eq 0 ]]; then
{
echo 'body<<EOF'
echo '**TruffleHog Scan Results** ✅'
echo ''
echo 'No secrets detected. All clear!'
echo 'EOF'
} >> "$GITHUB_OUTPUT"
else
# Generate findings list
FINDINGS=$(jq -r '.[] |
"- " +
(if .Verified then "**VERIFIED SECRET**" else "**Possible secret**" 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")
ACTION_TEXT=""
if [[ $VERIFIED -gt 0 ]]; then
ACTION_TEXT="**ACTION REQUIRED:** Rotate verified credentials immediately."
else
ACTION_TEXT="**Review:** Check if unverified secrets are false positives."
fi
{
echo 'body<<EOF'
echo "**TruffleHog Scan Results**"
echo ''
echo "**Summary:** Found ${TOTAL} potential secrets (${VERIFIED} verified, ${UNVERIFIED} unverified)"
echo ''
echo "${FINDINGS}"
echo ''
echo "${ACTION_TEXT}"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
fi
- name: Post PR comment
if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.scan.outputs.total > 0 }}
uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
with:
# We retain the final trufflehog-secret-scan-comment marker in case we want to chang the action to another one.
message: |
${{ steps.comment-body.outputs.body }}
<!-- trufflehog-secret-scan-comment -->
message-id: trufflehog-secret-scan-comment
- name: Create scan report
env:
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
{
echo "TruffleHog Scan Report"
echo "====================="
echo "Date: $(date)"
echo "Repository: ${{ github.repository }}"
echo "Branch: ${GITHUB_REF_NAME}"
echo "Commit: ${{ github.sha }}"
echo ""
echo "Summary:"
echo "- Total secrets: ${{ steps.scan.outputs.total }}"
echo "- Verified: ${{ steps.scan.outputs.verified }}"
echo "- Unverified: ${{ steps.scan.outputs.unverified }}"
echo ""
echo "Detailed Results:"
if [[ -f "results.json" && -s "results.json" ]] && jq empty results.json 2>/dev/null; then
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"
else
echo "No secrets detected"
fi
} > trufflehog_scan.txt
- name: Upload scan results
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
if: always()
with:
name: trufflehog_scan
path: |
trufflehog_scan.txt
results.json
if-no-files-found: warn
retention-days: 2
- name: Check failure policy
env:
FAIL_ON_VERIFIED: ${{ inputs.fail-on-verified }}
FAIL_ON_UNVERIFIED: ${{ inputs.fail-on-unverified }}
VERIFIED_COUNT: ${{ steps.scan.outputs.verified }}
UNVERIFIED_COUNT: ${{ steps.scan.outputs.unverified }}
run: |
SHOULD_FAIL=false
if [[ "${FAIL_ON_VERIFIED}" == "true" && "${VERIFIED_COUNT}" != "0" ]]; then
SHOULD_FAIL=true
fi
if [[ "${FAIL_ON_UNVERIFIED}" == "true" && "${UNVERIFIED_COUNT}" != "0" ]]; then
SHOULD_FAIL=true
fi
if [[ "${SHOULD_FAIL}" == "true" ]]; then
echo "Workflow failed due to secrets found (verified: ${VERIFIED_COUNT}, unverified: ${UNVERIFIED_COUNT})"
exit 1
else
echo "No action needed - secrets within configured thresholds"
fi