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}` });