Security Scan #658
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Security Scan | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main] | |
| schedule: | |
| # Run daily at 2 AM UTC | |
| - cron: '0 2 * * *' | |
| jobs: | |
| security-checks: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # Full history for git-secrets | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run npm audit | |
| id: npm-audit | |
| run: | | |
| npm audit --json > npm-audit.json || true | |
| HIGH_VULNS=$(cat npm-audit.json | jq '.metadata.vulnerabilities.high // 0') | |
| CRITICAL_VULNS=$(cat npm-audit.json | jq '.metadata.vulnerabilities.critical // 0') | |
| echo "high_vulns=$HIGH_VULNS" >> $GITHUB_OUTPUT | |
| echo "critical_vulns=$CRITICAL_VULNS" >> $GITHUB_OUTPUT | |
| continue-on-error: true | |
| - name: Run Checkov | |
| id: checkov | |
| uses: bridgecrewio/checkov-action@v12 | |
| with: | |
| directory: . | |
| framework: cloudformation | |
| output_format: json | |
| output_file_path: checkov-report.json | |
| quiet: true | |
| continue-on-error: true | |
| - name: Run git-secrets | |
| id: git-secrets | |
| run: | | |
| # Install git-secrets | |
| git clone https://github.com/awslabs/git-secrets.git | |
| cd git-secrets && sudo make install && cd .. | |
| rm -rf git-secrets | |
| # Configure git-secrets | |
| git secrets --register-aws | |
| git secrets --install | |
| # Allow placeholder AWS account ID used in tests and CDK examples | |
| # 123456789012 is the standard placeholder used in AWS docs and CDK | |
| git secrets --add --allowed '123456789012' | |
| # Scan only tracked files in the current commit (not history or untracked files) | |
| # Using git ls-files to list only tracked files, avoiding: | |
| # 1. Git history (old commits that may have since been cleaned) | |
| # 2. Untracked/ignored files like cdk.context.json, .env, CLAUDE.local.md | |
| if git ls-files -z | xargs -0 git secrets --scan --; then | |
| echo "secrets_found=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "secrets_found=true" >> $GITHUB_OUTPUT | |
| echo "::error::Secrets detected in repository!" | |
| fi | |
| continue-on-error: true | |
| - name: Run custom security check | |
| id: custom-check | |
| run: | | |
| chmod +x ./security-check.sh | |
| if ./security-check.sh; then | |
| echo "issues_found=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "issues_found=true" >> $GITHUB_OUTPUT | |
| fi | |
| continue-on-error: true | |
| - name: SAST with Semgrep | |
| uses: semgrep/semgrep-action@v1 | |
| with: | |
| config: >- | |
| p/security-audit | |
| p/typescript | |
| continue-on-error: true | |
| - name: Upload security reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: security-reports | |
| path: | | |
| checkov-report.json | |
| npm-audit.json | |
| security-findings.json | |
| retention-days: 3 | |
| - name: Comment PR with security summary | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| env: | |
| # Use environment variables for any dynamic data | |
| NPM_HIGH: ${{ steps.npm-audit.outputs.high_vulns }} | |
| NPM_CRITICAL: ${{ steps.npm-audit.outputs.critical_vulns }} | |
| SECRETS_FOUND: ${{ steps.git-secrets.outputs.secrets_found }} | |
| CUSTOM_ISSUES: ${{ steps.custom-check.outputs.issues_found }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Read security findings safely | |
| let findings = {}; | |
| try { | |
| const data = fs.readFileSync('security-findings.json', 'utf8'); | |
| findings = JSON.parse(data); | |
| } catch (e) { | |
| console.log('No security findings file found'); | |
| } | |
| // Use environment variables instead of direct GitHub context | |
| const npmHigh = parseInt(process.env.NPM_HIGH || '0'); | |
| const npmCritical = parseInt(process.env.NPM_CRITICAL || '0'); | |
| const secretsFound = process.env.SECRETS_FOUND === 'true'; | |
| const customIssues = process.env.CUSTOM_ISSUES === 'true'; | |
| // Create comment with sanitized data | |
| const critical = findings.summary?.critical || 0; | |
| const high = findings.summary?.high || 0; | |
| const medium = findings.summary?.medium || 0; | |
| const low = findings.summary?.low || 0; | |
| const score = findings.summary?.security_score || 'N/A'; | |
| let status = 'β All checks passed'; | |
| if (secretsFound) { | |
| status = 'π΄ SECRETS DETECTED - Do not merge!'; | |
| } else if (critical > 0 || npmCritical > 0) { | |
| status = 'π΄ CRITICAL ISSUES - Do not merge!'; | |
| } else if (high > 0 || npmHigh > 0) { | |
| status = 'π‘ HIGH ISSUES - Review required!'; | |
| } | |
| const comment = `## π Security Scan Results | |
| ### Status: ${status} | |
| ### Summary | |
| - **Critical Issues:** ${critical} | |
| - **High Issues:** ${high} | |
| - **Medium Issues:** ${medium} | |
| - **Low Issues:** ${low} | |
| ### NPM Vulnerabilities | |
| - **Critical:** ${npmCritical} | |
| - **High:** ${npmHigh} | |
| ### Security Score: ${score}/10 | |
| ${secretsFound ? 'β οΈ **SECRETS FOUND IN CODE - Must be removed!**\n' : ''} | |
| ${critical > 0 ? 'β οΈ **CRITICAL ISSUES FOUND - Do not merge!**\n' : ''} | |
| ${high > 0 ? 'β οΈ **HIGH ISSUES FOUND - Review required!**\n' : ''} | |
| Please run \`./security-check.sh\` locally for detailed findings. | |
| π [View detailed reports](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) | |
| `; | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| // Fail the workflow if critical issues are found | |
| if (secretsFound || critical > 0 || npmCritical > 0) { | |
| core.setFailed('Critical security issues detected'); | |
| } | |
| dependency-check: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Run npm audit | |
| run: | | |
| npm ci | |
| npm audit --audit-level=high || true | |
| continue-on-error: true | |
| infrastructure-scan: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| - name: Install CDK | |
| run: | | |
| npm install -g aws-cdk | |
| npm ci | |
| npm run build | |
| - name: CDK Synth for validation | |
| env: | |
| CDK_DEFAULT_ACCOUNT: '123456789012' | |
| CDK_DEFAULT_REGION: 'us-east-1' | |
| run: | | |
| npx cdk synth --all \ | |
| --context vpcId=vpc-test \ | |
| --context hostedZoneId=Z123456 \ | |
| --context domainName=example.com \ | |
| --context subDomain=test \ | |
| --context region=us-east-1 | |
| continue-on-error: true | |
| - name: Scan CloudFormation with cfn-lint | |
| run: | | |
| pip install cfn-lint | |
| find cdk.out -name "*.json" -type f | xargs cfn-lint | |
| continue-on-error: true | |
| - name: Create Security Summary | |
| if: always() | |
| run: | | |
| echo "## Security Scan Complete" > security-summary.md | |
| echo "Date: $(date -u +%Y-%m-%d)" >> security-summary.md | |
| echo "Commit: ${{ github.sha }}" >> security-summary.md | |
| echo "" >> security-summary.md | |
| echo "Review the security reports in the artifacts section." >> security-summary.md | |
| - name: Upload summary | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: security-summary | |
| path: security-summary.md | |
| retention-days: 3 |