262 lines
9.3 KiB
YAML
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}`
|
|
});
|