Skip to content
Closed
Changes from all commits
Commits
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
82 changes: 74 additions & 8 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ jobs:
- name: Checkout Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Cosign
id: cosign
continue-on-error: true
# v4 of the action install v3 of the CLI. v4 of the CLI will deprecate some features so be aware.
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Pin Trufflehog to a known good release
id: trufflehog_release
if: ${{ steps.cosign.outcome == 'success' }}
continue-on-error: true
shell: bash
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -35,14 +39,17 @@ jobs:
done

if [[ -z "$LATEST_TAG_NAME" ]]; then
echo "::error::No usable TruffleHog release found"
echo "::warning::No usable TruffleHog release found"
exit 1
fi

echo "Using TruffleHog version: $LATEST_TAG_NAME"
echo "latest_tag_name=$LATEST_TAG_NAME" >> "$GITHUB_OUTPUT"
echo "latest_release=${LATEST_TAG_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Download and verify TruffleHog release
id: download
if: ${{ steps.trufflehog_release.outcome == 'success' }}
continue-on-error: true
run: |
curl -fsSL --retry 8 --retry-max-time 120 --retry-all-errors -w '%{url_effective} %{http_code}\n' -O https://github.com/trufflesecurity/trufflehog/releases/download/${{ steps.trufflehog_release.outputs.latest_tag_name }}/trufflehog_${{ steps.trufflehog_release.outputs.latest_release }}_checksums.txt
curl -fsSL --retry 8 --retry-max-time 120 --retry-all-errors -w '%{url_effective} %{http_code}\n' -O https://github.com/trufflesecurity/trufflehog/releases/download/${{ steps.trufflehog_release.outputs.latest_tag_name }}/trufflehog_${{ steps.trufflehog_release.outputs.latest_release }}_checksums.txt.pem
Expand All @@ -57,28 +64,61 @@ jobs:

sha256sum --ignore-missing -c trufflehog_${{ steps.trufflehog_release.outputs.latest_release }}_checksums.txt
- name: Extract TruffleHog
id: extract
if: ${{ steps.download.outcome == 'success' }}
continue-on-error: true
run: |
tar xzf trufflehog_${{ steps.trufflehog_release.outputs.latest_release }}_linux_amd64.tar.gz -C /usr/local/bin
chmod +x /usr/local/bin/trufflehog
- name: Run TruffleHog scan
continue-on-error: true
id: scan
if: ${{ steps.extract.outcome == 'success' }}
continue-on-error: true
run: |
set +e
if [ -e .secret_scan_ignore ]; then
trufflehog git file://. --only-verified --github-actions --fail --exclude-paths=.secret_scan_ignore --exclude-detectors="datadogtoken,lob"
trufflehog git file://. --only-verified --json --fail --exclude-paths=.secret_scan_ignore --exclude-detectors="datadogtoken,lob" > "$RUNNER_TEMP/findings.jsonl"
else
trufflehog git file://. --only-verified --github-actions --fail --exclude-detectors="datadogtoken,lob"
trufflehog git file://. --only-verified --json --fail --exclude-detectors="datadogtoken,lob" > "$RUNNER_TEMP/findings.jsonl"
fi
exit_code=$?
set -e

findings=$(grep -c '[^[:space:]]' "$RUNNER_TEMP/findings.jsonl" || true)
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
echo "findings=$findings" >> "$GITHUB_OUTPUT"

# --json takes the stdout --github-actions used, so re-emit annotations here.
jq -r '"::error file=\(.SourceMetadata.Data.Git.file // "unknown"),line=\(.SourceMetadata.Data.Git.line // 1)::Verified \(.DetectorName) secret detected"' "$RUNNER_TEMP/findings.jsonl"

exit "$exit_code"
- name: Report scan skipped
if: ${{ !cancelled() && steps.scan.outcome == 'skipped' }}
env:
COSIGN_OUTCOME: ${{ steps.cosign.outcome }}
RELEASE_OUTCOME: ${{ steps.trufflehog_release.outcome }}
DOWNLOAD_OUTCOME: ${{ steps.download.outcome }}
EXTRACT_OUTCOME: ${{ steps.extract.outcome }}
run: |
echo "::warning::TruffleHog unavailable, this change was not scanned: cosign=$COSIGN_OUTCOME release=$RELEASE_OUTCOME download=$DOWNLOAD_OUTCOME extract=$EXTRACT_OUTCOME"
- name: Send Alert to SIEM
id: alert
if: ${{ !cancelled() }}
env:
SIEM_WEBHOOK_URL: ${{ vars.SECRET_SCAN_SIEM_WEBHOOK_URL }}
SCAN_OUTCOME: ${{ steps.scan.outcome }}
SCAN_EXIT_CODE: ${{ steps.scan.outputs.exit_code }}
SCAN_FINDINGS: ${{ steps.scan.outputs.findings }}
COSIGN_OUTCOME: ${{ steps.cosign.outcome }}
RELEASE_OUTCOME: ${{ steps.trufflehog_release.outcome }}
DOWNLOAD_OUTCOME: ${{ steps.download.outcome }}
EXTRACT_OUTCOME: ${{ steps.extract.outcome }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
PR_ACTOR: ${{ github.event.pull_request.user.login }}
EVENT_ACTOR: ${{ github.actor }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [[ -z "$SIEM_WEBHOOK_URL" ]]; then
exit 0
Expand All @@ -94,19 +134,45 @@ jobs:
pull_request=""
fi

findings="${SCAN_FINDINGS:-0}"
failed_step=""
if [[ "$SCAN_OUTCOME" == "success" ]]; then
status="success"
elif [[ "$SCAN_EXIT_CODE" == "183" || "$findings" -gt 0 ]]; then # 183: verified secret
status="failure"
else
status="cancelled" # no verdict reached, so not a detection
# Listed in execution order: the first stage that did not succeed is the one that broke.
for stage in "install_cosign:$COSIGN_OUTCOME" \
"pin_trufflehog_release:$RELEASE_OUTCOME" \
"download_and_verify_trufflehog:$DOWNLOAD_OUTCOME" \
"extract_trufflehog:$EXTRACT_OUTCOME" \
"run_trufflehog_scan:$SCAN_OUTCOME"; do
if [[ "${stage#*:}" != "success" ]]; then
failed_step="${stage%%:*}"
break
fi
done
fi

jq -n \
--arg event "github_secret_scanning" \
--arg status "$SCAN_OUTCOME" \
--arg status "$status" \
--arg failedStep "$failed_step" \
--arg exitCode "$SCAN_EXIT_CODE" \
--arg findings "$findings" \
--arg createdAt "$created_at" \
--arg repo "$REPO" \
--arg pull_request "$pull_request" \
--arg actor "$actor" \
'{event: $event, status: $status, createdAt: $createdAt, repo: $repo, pull_request: $pull_request, actor: $actor}' \
--arg runUrl "$RUN_URL" \
'{event: $event, status: $status, failedStep: $failedStep, exitCode: $exitCode, findings: $findings, createdAt: $createdAt, repo: $repo, pull_request: $pull_request, actor: $actor, runUrl: $runUrl}' \
| curl --fail --silent --show-error \
-H "Content-Type: application/json" \
--data @- \
"$SIEM_WEBHOOK_URL" \
|| echo "::warning::SIEM alert failed (non-blocking)"
- name: Fail workflow if secret detected
if: ${{ !cancelled() && steps.scan.outcome != 'success' }}
# A skipped scan does not block, only one that ran and failed does.
- name: Fail workflow if scan did not pass
if: ${{ !cancelled() && (steps.scan.outcome == 'failure' || (steps.scan.outputs.findings != '' && steps.scan.outputs.findings != '0')) }}
run: exit 1
Loading