262 lines
9.3 KiB
YAML

name: Security and Dependency Scanning
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
schedule:
# Run security scan daily at 3 AM UTC
- cron: '0 3 * * *'
workflow_dispatch:
env:
NODE_VERSION: '20'
jobs:
dependency-scan:
name: Dependency Security Scan
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Run npm audit
run: |
echo "Running npm security audit..."
npm audit --audit-level=moderate --json > audit-results.json || true
# Extract vulnerability counts
HIGH_VULNS=$(cat audit-results.json | jq '.metadata.vulnerabilities.high // 0')
CRITICAL_VULNS=$(cat audit-results.json | jq '.metadata.vulnerabilities.critical // 0')
echo "High vulnerabilities: $HIGH_VULNS"
echo "Critical vulnerabilities: $CRITICAL_VULNS"
# Fail if critical vulnerabilities found
if [ "$CRITICAL_VULNS" -gt 0 ]; then
echo "❌ Critical vulnerabilities found!"
cat audit-results.json | jq '.vulnerabilities[] | select(.severity == "critical")'
exit 1
fi
# Warn if high vulnerabilities found
if [ "$HIGH_VULNS" -gt 0 ]; then
echo "⚠️ High vulnerabilities found!"
cat audit-results.json | jq '.vulnerabilities[] | select(.severity == "high")'
fi
- name: License check
run: |
echo "Checking package licenses..."
npx license-checker --summary --onlyAllow 'MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;Unlicense;CC0-1.0' || {
echo "⚠️ Some packages have non-approved licenses"
echo "Run 'npx license-checker --summary' to see details"
}
- name: Check for outdated packages
run: |
echo "Checking for outdated packages..."
npm outdated --json > outdated-packages.json || true
# Count outdated packages
OUTDATED_COUNT=$(cat outdated-packages.json | jq 'length')
echo "Outdated packages: $OUTDATED_COUNT"
if [ "$OUTDATED_COUNT" -gt 0 ]; then
echo "⚠️ Found $OUTDATED_COUNT outdated packages"
cat outdated-packages.json | jq 'keys[]'
fi
- name: Upload security results
if: always()
uses: actions/upload-artifact@v4
with:
name: security-scan-results
path: |
audit-results.json
outdated-packages.json
retention-days: 30
code-security-scan:
name: Code Security Scan
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Install security tools
run: |
npm install -g @eslint/eslintrc
npm install -g eslint-plugin-security
- name: Security linting
run: |
echo "Running security-focused linting..."
# Check for common security issues
if grep -r "eval(" --include="*.js" --include="*.ts" --include="*.tsx" .; then
echo "❌ Found eval() usage - potential security risk"
exit 1
fi
if grep -r "innerHTML" --include="*.js" --include="*.ts" --include="*.tsx" .; then
echo "⚠️ Found innerHTML usage - review for XSS risks"
fi
if grep -r "dangerouslySetInnerHTML" --include="*.js" --include="*.ts" --include="*.tsx" .; then
echo "⚠️ Found dangerouslySetInnerHTML usage - review for XSS risks"
fi
- name: Check for hardcoded secrets
run: |
echo "Checking for potential hardcoded secrets..."
# Check for common secret patterns
if grep -rE "(password|secret|key|token).*=.*['\"][^'\"]{8,}['\"]" --include="*.js" --include="*.ts" --include="*.tsx" --exclude-dir=node_modules .; then
echo "⚠️ Potential hardcoded secrets found - review manually"
fi
# Check for API keys
if grep -rE "(api[_-]?key|apikey)" --include="*.js" --include="*.ts" --include="*.tsx" --exclude-dir=node_modules .; then
echo "⚠️ Potential API key references found - ensure no hardcoded keys"
fi
- name: Check environment variable usage
run: |
echo "Checking environment variable usage..."
# Ensure sensitive data uses environment variables
if grep -r "process\.env\." --include="*.js" --include="*.ts" --include="*.tsx" .; then
echo "✅ Environment variables are being used"
fi
container-security:
name: Container Security Scan
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check Dockerfile security
run: |
if [ -f "Dockerfile" ]; then
echo "Checking Dockerfile security..."
# Check for root user
if grep -q "USER root" Dockerfile; then
echo "⚠️ Dockerfile runs as root - consider using non-root user"
fi
# Check for latest tags
if grep -q ":latest" Dockerfile; then
echo "⚠️ Dockerfile uses 'latest' tag - consider pinning versions"
fi
# Check for security updates
if grep -q "apt-get update" Dockerfile; then
echo "✅ Dockerfile includes package updates"
fi
else
echo "No Dockerfile found - skipping container security check"
fi
security-report:
name: Generate Security Report
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [dependency-scan, code-security-scan, container-security]
if: always()
steps:
- name: Download security results
uses: actions/download-artifact@v4
with:
name: security-scan-results
path: security-results/
- name: Generate security report
run: |
echo "# Security Scan Report" > security-report.md
echo "Generated: $(date -u)" >> security-report.md
echo "" >> security-report.md
# Add dependency scan results
if [ -f "security-results/audit-results.json" ]; then
echo "## Dependency Security" >> security-report.md
echo "" >> security-report.md
CRITICAL=$(cat security-results/audit-results.json | jq '.metadata.vulnerabilities.critical // 0')
HIGH=$(cat security-results/audit-results.json | jq '.metadata.vulnerabilities.high // 0')
MODERATE=$(cat security-results/audit-results.json | jq '.metadata.vulnerabilities.moderate // 0')
LOW=$(cat security-results/audit-results.json | jq '.metadata.vulnerabilities.low // 0')
echo "- Critical: $CRITICAL" >> security-report.md
echo "- High: $HIGH" >> security-report.md
echo "- Moderate: $MODERATE" >> security-report.md
echo "- Low: $LOW" >> security-report.md
echo "" >> security-report.md
fi
# Add outdated packages
if [ -f "security-results/outdated-packages.json" ]; then
echo "## Outdated Packages" >> security-report.md
echo "" >> security-report.md
OUTDATED_COUNT=$(cat security-results/outdated-packages.json | jq 'length')
echo "Total outdated packages: $OUTDATED_COUNT" >> security-report.md
echo "" >> security-report.md
fi
echo "## Scan Status" >> security-report.md
echo "" >> security-report.md
echo "- Dependency Scan: ${{ needs.dependency-scan.result }}" >> security-report.md
echo "- Code Security Scan: ${{ needs.code-security-scan.result }}" >> security-report.md
echo "- Container Security: ${{ needs.container-security.result }}" >> security-report.md
- name: Upload security report
uses: actions/upload-artifact@v4
with:
name: security-report
path: security-report.md
retention-days: 90
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('security-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🔒 Security Scan Results
${report}`
});