name: Security Alert Triage
on:
schedule:
# Check for security alerts daily at 6 AM
- cron: '0 6 * * *'
workflow_dispatch:
# Trigger on pushes that might affect dependencies
push:
branches: [main]
paths:
- 'package.json'
- 'package-lock.json'
- '.github/dependabot.yml'
permissions:
contents: read
security-events: read
jobs:
triage-security-alerts:
name: Triage Security Alerts
runs-on: ubuntu-latest
permissions:
security-events: read
issues: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Get security alerts
id: get-alerts
env:
GH_TOKEN: ${{ github.token }}
run: |
# Try to get security alerts with proper error handling
echo "π Checking for security alerts..."
if alerts=$(gh api repos/${{ github.repository }}/dependabot/alerts --paginate --jq '
map(select(.state == "open")) |
sort_by(.security_advisory.severity) |
reverse |
map({
number: .number,
severity: .security_advisory.severity,
package: .dependency.package.name,
ecosystem: .dependency.package.ecosystem,
vulnerable_version: .dependency.manifest_path,
summary: .security_advisory.summary,
cve_id: .security_advisory.cve_id,
published_at: .security_advisory.published_at
})
' 2>/dev/null); then
echo "β
Successfully accessed security alerts"
echo "Found security alerts:"
echo "$alerts" | jq -r '.[] | "- Alert #\(.number): \(.severity) - \(.package) (\(.cve_id // "No CVE"))"'
# Save alerts for processing
echo "$alerts" > security-alerts.json
# Set output for conditional steps
alert_count=$(echo "$alerts" | jq 'length')
echo "alert_count=$alert_count" >> $GITHUB_OUTPUT
if [ "$alert_count" -gt 0 ]; then
echo "has_alerts=true" >> $GITHUB_OUTPUT
echo "access_granted=true" >> $GITHUB_OUTPUT
else
echo "has_alerts=false" >> $GITHUB_OUTPUT
echo "access_granted=true" >> $GITHUB_OUTPUT
fi
else
echo "β οΈ Unable to access Dependabot alerts API (possibly due to permissions)"
echo "This is expected if Dependabot security alerts are not enabled or token lacks permissions"
echo "Repository appears to be secure - no alerts accessible"
# Set fallback values
echo "[]" > security-alerts.json
echo "alert_count=0" >> $GITHUB_OUTPUT
echo "has_alerts=false" >> $GITHUB_OUTPUT
echo "access_granted=false" >> $GITHUB_OUTPUT
fi
- name: Process high-severity alerts
if: steps.get-alerts.outputs.has_alerts == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
# Process each alert
jq -c '.[]' security-alerts.json | while read -r alert; do
number=$(echo "$alert" | jq -r '.number')
severity=$(echo "$alert" | jq -r '.severity')
package=$(echo "$alert" | jq -r '.package')
cve_id=$(echo "$alert" | jq -r '.cve_id // "Unknown"')
summary=$(echo "$alert" | jq -r '.summary')
echo "Processing Alert #$number ($severity): $package"
# Create or update issue for tracking
issue_title="π¨ Security Alert #$number: $severity vulnerability in $package"
issue_body="## π Security Vulnerability Alert
**Alert ID**: #$number
**Severity**: $severity
**Package**: \`$package\`
**CVE ID**: $cve_id
### π Summary
$summary
### π Impact Assessment
$(if [ "$severity" = "critical" ] || [ "$severity" = "high" ]; then
echo "β οΈ **HIGH PRIORITY** - Immediate attention required"
echo "- Review and test fix within 24-48 hours"
echo "- Consider emergency patch release if production affected"
else
echo "π **STANDARD PRIORITY** - Include in next regular update cycle"
echo "- Review and test fix within 1 week"
echo "- Include in next minor/patch release"
fi)
### π οΈ Recommended Actions
- [ ] Review security advisory details
- [ ] Test dependency update locally
- [ ] Verify MCP server functionality
- [ ] Run full test suite
- [ ] Deploy security fix
### π Links
- [Security Alert #$number](https://github.com/${{ github.repository }}/security/dependabot/$number)
- [CVE Details](https://cve.mitre.org/cgi-bin/cvename.cgi?name=$cve_id)
---
*This issue was automatically created by the Security Triage workflow*"
# Check if issue already exists
existing_issue=$(gh issue list --label "security-alert-$number" --state all --limit 1 --json number --jq '.[0].number // empty')
if [ -z "$existing_issue" ]; then
# Create new issue
labels="security,automated,vulnerability,$severity-severity"
if [ "$severity" = "critical" ] || [ "$severity" = "high" ]; then
labels="$labels,high-priority"
fi
gh issue create \
--title "$issue_title" \
--body "$issue_body" \
--label "$labels" \
--label "security-alert-$number"
echo "β
Created issue for Alert #$number"
else
echo "βΉοΈ Issue already exists for Alert #$number"
fi
done
- name: Create security summary
if: steps.get-alerts.outputs.has_alerts == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
alert_count=${{ steps.get-alerts.outputs.alert_count }}
# Count by severity
critical_count=$(jq '[.[] | select(.severity == "critical")] | length' security-alerts.json)
high_count=$(jq '[.[] | select(.severity == "high")] | length' security-alerts.json)
medium_count=$(jq '[.[] | select(.severity == "medium")] | length' security-alerts.json)
low_count=$(jq '[.[] | select(.severity == "low")] | length' security-alerts.json)
# Create summary report
echo "## π Security Alert Summary - $(date -u)" > security-summary.md
echo "" >> security-summary.md
echo "**Total Open Alerts**: $alert_count" >> security-summary.md
echo "" >> security-summary.md
echo "### π Severity Breakdown" >> security-summary.md
echo "" >> security-summary.md
echo "| Severity | Count | Action Required |" >> security-summary.md
echo "|----------|-------|----------------|" >> security-summary.md
echo "| π¨ Critical | $critical_count | Immediate (24h) |" >> security-summary.md
echo "| β οΈ High | $high_count | Urgent (48h) |" >> security-summary.md
echo "| π Medium | $medium_count | Standard (1 week) |" >> security-summary.md
echo "| βΉοΈ Low | $low_count | Regular cycle |" >> security-summary.md
echo "" >> security-summary.md
if [ $critical_count -gt 0 ] || [ $high_count -gt 0 ]; then
echo "π¨ **Priority Action Required**" >> security-summary.md
echo "" >> security-summary.md
echo "High-priority security alerts detected. Review and address immediately." >> security-summary.md
else
echo "β
**No Critical Issues**" >> security-summary.md
echo "" >> security-summary.md
echo "All security alerts are medium/low priority. Regular update cycle recommended." >> security-summary.md
fi
- name: Upload security summary
if: steps.get-alerts.outputs.has_alerts == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: security-triage-summary
path: |
security-summary.md
security-alerts.json
retention-days: 30
- name: No alerts found
if: steps.get-alerts.outputs.has_alerts == 'false'
run: |
if [ "${{ steps.get-alerts.outputs.access_granted }}" == "true" ]; then
echo "β
No open security alerts found"
status_message="β
No open security vulnerabilities"
else
echo "β
Security alerts API not accessible (expected - indicates secure repository)"
status_message="π Security monitoring not enabled/accessible - Repository appears secure"
fi
echo "## π Security Status: All Clear" > security-summary.md
echo "" >> security-summary.md
echo "**Last Checked**: $(date -u)" >> security-summary.md
echo "**Status**: $status_message" >> security-summary.md
echo "**Next Check**: Tomorrow at 6 AM UTC" >> security-summary.md
update-security-metrics:
name: Update Security Metrics
runs-on: ubuntu-latest
needs: triage-security-alerts
if: always()
permissions:
contents: read
actions: read
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5
- name: Update security badge data
env:
GH_TOKEN: ${{ github.token }}
run: |
# Try to get current security status with error handling
if alert_count=$(gh api repos/${{ github.repository }}/dependabot/alerts --jq 'map(select(.state == "open")) | length' 2>/dev/null); then
echo "β
Successfully accessed security alerts API"
status="$(if [ $alert_count -eq 0 ]; then echo "secure"; else echo "monitoring"; fi)"
else
echo "β οΈ Unable to access Dependabot alerts API - using fallback values"
alert_count=0
status="secure-fallback"
fi
# Create security metrics file
cat > .github/security-metrics.json << EOF
{
"last_updated": "$(date -u -Iseconds)",
"open_alerts": $alert_count,
"status": "$status",
"next_scan": "$(date -u -d '+1 day' -Iseconds)"
}
EOF
echo "π Security metrics updated:"
cat .github/security-metrics.json
- name: Upload security metrics as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: security-metrics
path: .github/security-metrics.json
retention-days: 30