Skip to content

Security Scan

Security Scan #659

Workflow file for this run

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