From b48c0a8faec79ee7169321c2f69effc632f25018 Mon Sep 17 00:00:00 2001 From: shodan-wb Date: Tue, 30 Jun 2026 18:43:33 -0700 Subject: [PATCH] Add secret scanning workflow (Gitleaks + TruffleHog + Drata) Adds automated secret scanning on every push, PR, and weekly schedule. Gitleaks detects hardcoded credentials, TruffleHog verifies live secrets, and results are reported to Drata for CMMC compliance evidence. --- .github/workflows/secret-scan.yml | 171 ++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 .github/workflows/secret-scan.yml diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..cb68940 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,171 @@ +name: Secret Scanning + +on: + pull_request: + push: + branches: [main, master] + schedule: + - cron: '0 9 * * 1' + +jobs: + gitleaks: + name: Gitleaks + runs-on: ubuntu-latest + outputs: + secrets_found: ${{ steps.count.outputs.secrets_found }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Gitleaks + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz" \ + | tar xz -C /usr/local/bin gitleaks + + - name: Write org-wide allowlist + run: | + cat > .gitleaks.toml << 'GITLEAKS_EOF' + [extend] + useDefault = true + + [allowlist] + paths = [ + '''db/structure\.sql''', + '''db/schema\.rb''', + '''routesforllms\.txt''', + '''\.sarif$''', + ] + regexes = [ + '''pk\.eyJ1''', + '''YOUR_API_KEY''', + '''YOUR_CLIENT_ID''', + ] + GITLEAKS_EOF + + - name: Run Gitleaks + id: scan + run: | + gitleaks detect \ + --source . \ + --config .gitleaks.toml \ + --report-format sarif \ + --report-path gitleaks-results.sarif \ + --exit-code 2 + continue-on-error: true + + - name: Count findings + id: count + if: always() + run: | + if [ -f gitleaks-results.sarif ]; then + COUNT=$(jq '[.runs[].results[]] | length' gitleaks-results.sarif 2>/dev/null || echo 0) + else + COUNT=0 + fi + echo "secrets_found=$COUNT" >> "$GITHUB_OUTPUT" + + - name: Upload SARIF to Security tab + if: always() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: gitleaks-results.sarif + continue-on-error: true + + - name: Upload scan artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: gitleaks-results + path: gitleaks-results.sarif + + - name: Fail if secrets found + if: steps.scan.outcome == 'failure' + run: exit 1 + + trufflehog: + name: TruffleHog (Verified Secrets) + runs-on: ubuntu-latest + outputs: + secrets_found: ${{ steps.count.outputs.secrets_found }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: TruffleHog Scan + id: scan + uses: trufflesecurity/trufflehog@v3.88.1 + with: + extra_args: --only-verified + continue-on-error: true + + - name: Set result + id: count + if: always() + run: | + if [ "${{ steps.scan.outcome }}" = "failure" ]; then + echo "secrets_found=1" >> "$GITHUB_OUTPUT" + else + echo "secrets_found=0" >> "$GITHUB_OUTPUT" + fi + + - name: Fail if secrets found + if: steps.scan.outcome == 'failure' + run: exit 1 + + report-to-drata: + name: Report to Drata + runs-on: ubuntu-latest + needs: [gitleaks, trufflehog] + if: always() + env: + DRATA_CONNECTION_ID: 11 + DRATA_CCT_API_KEY: ${{ secrets.DRATA_CCT_API_KEY }} + steps: + - name: Get resource ID + if: env.DRATA_CCT_API_KEY != '' + id: resource + run: | + RESOURCE_ID=$(curl -sf \ + -H "Authorization: Bearer ${{ env.DRATA_CCT_API_KEY }}" \ + "https://public-api.drata.com/public/v2/custom-connections/${{ env.DRATA_CONNECTION_ID }}?expand[]=customResources" \ + | jq -r '.customResources[0].id') + echo "id=$RESOURCE_ID" >> "$GITHUB_OUTPUT" + + - name: Submit scan results + if: env.DRATA_CCT_API_KEY != '' + run: | + GITLEAKS=${{ needs.gitleaks.outputs.secrets_found || 0 }} + TRUFFLEHOG=${{ needs.trufflehog.outputs.secrets_found || 0 }} + TOTAL=$((GITLEAKS + TRUFFLEHOG)) + if [ "$TOTAL" -eq 0 ]; then PASSED=true; else PASSED=false; fi + + curl -sf -X POST \ + -H "Authorization: Bearer ${{ env.DRATA_CCT_API_KEY }}" \ + -H "Content-Type: application/json" \ + "https://public-api.drata.com/public/v2/custom-connections/${{ env.DRATA_CONNECTION_ID }}/resources/${{ steps.resource.outputs.id }}/records" \ + -d "$(jq -n \ + --arg repo "${{ github.repository }}" \ + --arg scan_id "${{ github.repository }}-${{ github.run_id }}" \ + --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg sha "${{ github.sha }}" \ + --arg trigger "${{ github.event_name }}" \ + --argjson gitleaks "$GITLEAKS" \ + --argjson trufflehog "$TRUFFLEHOG" \ + --argjson total "$TOTAL" \ + --argjson passed "$PASSED" \ + --arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + '{data: { + repo_name: $repo, + scan_id: $scan_id, + scan_timestamp: $ts, + commit_sha: $sha, + trigger: $trigger, + gitleaks_secrets_found: $gitleaks, + trufflehog_verified_secrets_found: $trufflehog, + total_secrets_found: $total, + passed: $passed, + workflow_run_url: $url + }}' + )"