Skip to content

Centralized Secret Scanning Report #14

Centralized Secret Scanning Report

Centralized Secret Scanning Report #14

name: Centralized Secret Scanning Report
on:
workflow_dispatch:
inputs:
include_inactive:
description: 'Include inactive alerts in report'
required: false
type: boolean
default: false
max_workers:
description: 'Maximum number of concurrent workers'
required: false
type: number
default: 10
log_level:
description: 'Logging level'
required: false
type: choice
options:
- INFO
- DEBUG
- WARNING
- ERROR
default: 'INFO'
alert_threshold:
description: 'Number of active alerts to trigger issue creation'
required: false
type: number
default: 10
schedule:
- cron: '0 0 * * 1'
permissions:
security-events: read
contents: write # Corrected: covers both reading and writing
actions: write
issues: write
jobs:
generate-report:
runs-on: ubuntu-latest
steps:
- name: Checkout .github repo
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/.github # Checkout the .github repo!
ref: main # Or your default branch
path: .github # Checkout into a directory named .github
token: ${{ secrets.SECRET_SCANNING_TOKEN }} # Use PAT for org-level access.
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: .github/scripts/requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r .github/scripts/requirements.txt
- name: Generate timestamp
id: timestamp
run: echo "timestamp=$(date +%Y%m%d_%H%M%S)" >> $GITHUB_OUTPUT
- name: Generate Secret Report
id: generate-report
env:
GITHUB_TOKEN: ${{ secrets.SECRET_SCANNING_TOKEN }} # Use PAT for org-level access
ORGANIZATION: ${{ github.repository_owner }}
REPORT_FILE: "secret_report_${{ steps.timestamp.outputs.timestamp }}.csv"
run: |
# No need to create 'reports' dir here; it will be in the checked-out .github repo
python .github/scripts/github_secret_scanner.py \
--org "$ORGANIZATION" \
--token "$GITHUB_TOKEN" \
--output "reports/${REPORT_FILE}" \ # Path relative to workflow
--log-level ${{ inputs.log_level || 'INFO' }} \
--max-workers ${{ inputs.max_workers || 10 }} \
--max-retries 3 \
${{ inputs.include_inactive && '--include-inactive' || '' }}
echo "report_path=reports/${REPORT_FILE}" >> $GITHUB_OUTPUT # Path still correct.
- name: Check for No Repositories
id: check-repos
if: success()
run: |
if grep -q "__NO_REPOS__" ${{ steps.generate-report.outputs.report_path }}/../output.txt; then # Adjust the path
echo "No repositories found in the organization. Exiting."
exit 1
fi
- name: Process report statistics (inline)
id: stats
if: success() && steps.check-repos.outcome == 'success'
run: |
STATS=$(grep "__STATS_START__" ${{ steps.generate-report.outputs.report_path }}/../output.txt | sed 's/__STATS_START__//' | sed 's/__STATS_END__//') # Adjust path
echo "total_alerts=$(echo $STATS | cut -d',' -f1 | cut -d'=' -f2)" >> $GITHUB_OUTPUT
echo "active_alerts=$(echo $STATS | cut -d',' -f2 | cut -d'=' -f2)" >> $GITHUB_OUTPUT
echo "inactive_alerts=$(echo $STATS | cut -d',' -f3 | cut -d'=' -f2)" >> $GITHUB_OUTPUT
echo "Total alerts found: $(echo $STATS | cut -d',' -f1 | cut -d'=' -f2)"
echo "Active alerts: $(echo $STATS | cut -d',' -f2 | cut -d'=' -f2)"
echo "Inactive alerts: $(echo $STATS | cut -d',' -f3 | cut -d'=' -f2)"
- name: Create summary issue (using github-script)
if: success() && steps.check-repos.outcome == 'success' && steps.stats.outputs.active_alerts > inputs.alert_threshold
uses: actions/github-script@v7
with:
script: |
const stats = {
total: '${{ steps.stats.outputs.total_alerts }}',
active: '${{ steps.stats.outputs.active_alerts }}',
inactive: '${{ steps.stats.outputs.inactive_alerts }}',
};
const now = new Date();
const formattedDate = now.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const body = `
# Secret Scanning Report Summary
Report generated on: ${now.toISOString()}
## Statistics
- Total alerts analyzed: ${stats.total}
- Active alerts found: ${stats.active}
- Inactive alerts found: ${stats.inactive}
## Details
- Report artifact: [Download report](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
- Workflow run: [View details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
## Configuration
- Include inactive alerts: ${{ inputs.include_inactive || 'false' }}
- Max workers: ${{ inputs.max_workers || '10' }}
- Log level: ${{ inputs.log_level || 'INFO' }}
- Alert threshold: ${{ inputs.alert_threshold || '10'}}
`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: \`📊 Secret Scanning Report - \${formattedDate}\`,
body: body,
labels: ['secret-scanning', 'report']
});
- name: Commit and Push Report
if: success() && steps.check-repos.outcome == 'success'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Add secret scanning report: ${{ steps.timestamp.outputs.timestamp }}"
repository: ./.github # Commit to the .github directory
file_pattern: reports/*.csv # Commit the report file
commit_user_name: GitHub Actions # Set committer name
commit_user_email: [email protected] # Set committer email
commit_author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> # Set author.
push_options: '--force' # Use force push (be careful!)
- name: Notify on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
const body = `
# 🚨 Secret Scanning Report Generation Failed
Workflow run failed at ${new Date().toISOString()}
## Details
- Run ID: \`${context.runId}\`
- Trigger: ${context.eventName}
- Actor: @${context.actor}
## Links
- [View run details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
- [View workflow file](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/blob/main/.github/workflows/secret-scanning-report.yml)
Please check the workflow logs for detailed error information.
`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🚨 Secret Scanning Report Generation Failed',
body: body,
labels: ['secret-scanning', 'failed']
});
- name: Clean up
if: always()
run: |
# No need to clean reports dir now it is part of .github repo
echo "No clean up required."
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true