193 lines
6.4 KiB
YAML
193 lines
6.4 KiB
YAML
# GitHub Actions Workflow for Nuclei Security Scanning
|
|
# Place this file in .github/workflows/nuclei-scan.yml
|
|
|
|
name: Nuclei Security Scan
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, develop ]
|
|
pull_request:
|
|
branches: [ main ]
|
|
schedule:
|
|
# Run daily at 2 AM UTC
|
|
- cron: '0 2 * * *'
|
|
workflow_dispatch:
|
|
inputs:
|
|
target_url:
|
|
description: 'Target URL to scan'
|
|
required: true
|
|
default: 'https://staging.example.com'
|
|
severity:
|
|
description: 'Severity levels (comma-separated)'
|
|
required: false
|
|
default: 'critical,high'
|
|
|
|
env:
|
|
# Default target URL (override with workflow_dispatch input)
|
|
TARGET_URL: https://staging.example.com
|
|
# Severity levels to scan
|
|
SEVERITY: critical,high
|
|
# Template tags to use
|
|
TEMPLATE_TAGS: cve,owasp,misconfig
|
|
|
|
jobs:
|
|
nuclei-scan:
|
|
name: Nuclei Vulnerability Scan
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Run Nuclei Scan
|
|
id: nuclei
|
|
uses: projectdiscovery/nuclei-action@main
|
|
with:
|
|
target: ${{ github.event.inputs.target_url || env.TARGET_URL }}
|
|
severity: ${{ github.event.inputs.severity || env.SEVERITY }}
|
|
templates: ${{ env.TEMPLATE_TAGS }}
|
|
output: nuclei-results.jsonl
|
|
|
|
- name: Parse Nuclei Results
|
|
if: always()
|
|
run: |
|
|
# Install dependencies (if using custom parser)
|
|
# pip install -r requirements.txt
|
|
|
|
# Parse results and generate HTML report
|
|
if [ -f nuclei-results.jsonl ]; then
|
|
echo "Parsing Nuclei results..."
|
|
python3 scripts/parse_nuclei_results.py \
|
|
--input nuclei-results.jsonl \
|
|
--output nuclei-report.html \
|
|
--format html
|
|
|
|
# Count findings by severity
|
|
CRITICAL=$(grep -c '"severity":"critical"' nuclei-results.jsonl || echo 0)
|
|
HIGH=$(grep -c '"severity":"high"' nuclei-results.jsonl || echo 0)
|
|
TOTAL=$(wc -l < nuclei-results.jsonl)
|
|
|
|
echo "## Nuclei Scan Results" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Total Findings**: $TOTAL" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Critical**: $CRITICAL" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **High**: $HIGH" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Set outputs
|
|
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
|
|
echo "high=$HIGH" >> $GITHUB_OUTPUT
|
|
echo "total=$TOTAL" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "No findings detected" >> $GITHUB_STEP_SUMMARY
|
|
echo "critical=0" >> $GITHUB_OUTPUT
|
|
echo "high=0" >> $GITHUB_OUTPUT
|
|
echo "total=0" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Upload SARIF file
|
|
if: always()
|
|
uses: github/codeql-action/upload-sarif@v3
|
|
with:
|
|
sarif_file: nuclei.sarif
|
|
category: nuclei
|
|
|
|
- name: Upload Artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: nuclei-scan-results
|
|
path: |
|
|
nuclei-results.jsonl
|
|
nuclei-report.html
|
|
nuclei.sarif
|
|
retention-days: 30
|
|
|
|
- name: Comment on PR
|
|
if: github.event_name == 'pull_request' && always()
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const critical = '${{ steps.nuclei.outputs.critical || 0 }}';
|
|
const high = '${{ steps.nuclei.outputs.high || 0 }}';
|
|
const total = '${{ steps.nuclei.outputs.total || 0 }}';
|
|
|
|
const body = `## 🔒 Nuclei Security Scan Results
|
|
|
|
| Severity | Count |
|
|
|----------|-------|
|
|
| Critical | ${critical} |
|
|
| High | ${high} |
|
|
| **Total** | **${total}** |
|
|
|
|
${critical > 0 ? '⚠️ **Critical vulnerabilities detected!**' : ''}
|
|
${high > 0 ? '⚠️ High severity vulnerabilities detected.' : ''}
|
|
|
|
View detailed results in the [workflow artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).`;
|
|
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: body
|
|
});
|
|
|
|
- name: Fail on Critical Findings
|
|
if: steps.nuclei.outputs.critical > 0
|
|
run: |
|
|
echo "::error::Critical vulnerabilities detected!"
|
|
exit 1
|
|
|
|
- name: Notify on Slack
|
|
if: failure() && steps.nuclei.outputs.critical > 0
|
|
uses: slackapi/slack-github-action@v1
|
|
with:
|
|
payload: |
|
|
{
|
|
"text": "🚨 Critical vulnerabilities detected in ${{ github.repository }}",
|
|
"blocks": [
|
|
{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "*Critical Security Vulnerabilities Detected*\n\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Critical Findings:* ${{ steps.nuclei.outputs.critical }}\n*High Findings:* ${{ steps.nuclei.outputs.high }}\n\n<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Scan Results>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
env:
|
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
|
|
# Optional: Separate job for authenticated scanning
|
|
nuclei-authenticated-scan:
|
|
name: Nuclei Authenticated Scan
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main' # Only run on main branch
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Run Authenticated Scan
|
|
uses: projectdiscovery/nuclei-action@main
|
|
with:
|
|
target: ${{ env.TARGET_URL }}
|
|
severity: critical,high
|
|
templates: cve,owasp
|
|
# Add authentication headers from secrets
|
|
headers: |
|
|
Authorization: Bearer ${{ secrets.API_TOKEN }}
|
|
Cookie: session=${{ secrets.SESSION_COOKIE }}
|
|
output: nuclei-auth-results.jsonl
|
|
|
|
- name: Upload Authenticated Results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: nuclei-authenticated-scan-results
|
|
path: nuclei-auth-results.jsonl
|
|
retention-days: 30
|