Security Scanning #15
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 Scanning | |
on: | |
push: | |
branches: [ main ] | |
paths: | |
- 'src/**' | |
- 'package.json' | |
- 'bun.lock' | |
- '.github/workflows/security.yml' | |
schedule: | |
# Run security scan weekly on Monday at 2 AM UTC | |
- cron: '0 2 * * 1' | |
workflow_dispatch: | |
permissions: | |
contents: read | |
security-events: write | |
jobs: | |
dependency-audit: | |
name: Dependency Audit | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Bun | |
uses: oven-sh/setup-bun@v2 | |
with: | |
bun-version: '1.2.14' | |
- name: Install dependencies | |
run: bun install --frozen-lockfile | |
- name: Run Bun audit | |
run: | | |
echo "## 🔒 Security Audit Report" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Note: Bun doesn't have a built-in audit command yet, so we use alternative tools | |
# Check with npm audit for known vulnerabilities (works with package.json) | |
if bunx better-npm-audit audit --level moderate > audit-results.txt 2>&1; then | |
echo "✅ No security vulnerabilities found!" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "⚠️ Security vulnerabilities detected:" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo '```' >> $GITHUB_STEP_SUMMARY | |
cat audit-results.txt >> $GITHUB_STEP_SUMMARY || true | |
echo '```' >> $GITHUB_STEP_SUMMARY | |
fi | |
continue-on-error: true | |
- name: Check for known vulnerabilities | |
uses: aquasecurity/trivy-action@master | |
with: | |
scan-type: 'fs' | |
scan-ref: '.' | |
format: 'sarif' | |
output: 'trivy-results.sarif' | |
severity: 'CRITICAL,HIGH,MEDIUM' | |
ignore-unfixed: true | |
- name: Upload Trivy results to GitHub Security | |
uses: github/codeql-action/upload-sarif@v3 | |
if: always() | |
with: | |
sarif_file: 'trivy-results.sarif' | |
license-check: | |
name: License Compliance | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Bun | |
uses: oven-sh/setup-bun@v2 | |
with: | |
bun-version: '1.2.14' | |
- name: Install dependencies | |
run: bun install --frozen-lockfile | |
- name: Check licenses | |
run: | | |
echo "## 📋 License Compliance Report" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Install license checker | |
bunx license-checker --summary --excludePrivatePackages > license-summary.txt | |
# Display summary | |
echo "### License Summary" >> $GITHUB_STEP_SUMMARY | |
echo '```' >> $GITHUB_STEP_SUMMARY | |
cat license-summary.txt >> $GITHUB_STEP_SUMMARY | |
echo '```' >> $GITHUB_STEP_SUMMARY | |
# Check for problematic licenses | |
PROBLEMATIC_LICENSES="GPL|AGPL|LGPL|SSPL|EUPL" | |
if bunx license-checker --excludePrivatePackages --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;CC0-1.0;CC-BY-3.0;CC-BY-4.0;Unlicense;WTFPL" > /dev/null 2>&1; then | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "✅ All licenses are compatible!" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "⚠️ Some licenses may need review" >> $GITHUB_STEP_SUMMARY | |
fi | |
continue-on-error: true | |
semgrep: | |
name: Semgrep SAST | |
runs-on: ubuntu-latest | |
container: | |
image: semgrep/semgrep | |
if: (github.actor != 'dependabot[bot]') | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Run Semgrep CI | |
run: | | |
# Run Semgrep with local rules (no app token required) | |
semgrep ci \ | |
--config=auto \ | |
--config=p/security-audit \ | |
--config=p/typescript \ | |
--config=p/javascript \ | |
--config=p/nodejs \ | |
--config=p/owasp-top-ten \ | |
--json \ | |
--output=semgrep-results.json || true | |
# Generate summary | |
echo "## 🔍 SAST Scan Results" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
if [ -f semgrep-results.json ]; then | |
FINDINGS=$(jq '.results | length' semgrep-results.json) | |
if [ "$FINDINGS" -eq 0 ]; then | |
echo "✅ No security issues found by Semgrep!" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "⚠️ Found $FINDINGS potential security issues" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Show top 5 findings | |
echo "### Top Findings:" >> $GITHUB_STEP_SUMMARY | |
jq -r '.results[:5] | .[] | "- **\(.check_id)**: \(.path) (line \(.start.line))"' semgrep-results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
fi | |
fi | |
- name: Generate SARIF | |
if: always() | |
run: | | |
# Convert to SARIF for GitHub Security tab | |
semgrep ci \ | |
--config=auto \ | |
--config=p/security-audit \ | |
--config=p/typescript \ | |
--config=p/javascript \ | |
--config=p/nodejs \ | |
--config=p/owasp-top-ten \ | |
--sarif \ | |
--output=semgrep.sarif || true | |
- name: Upload SARIF | |
uses: github/codeql-action/upload-sarif@v3 | |
if: always() && hashFiles('semgrep.sarif') != '' | |
with: | |
sarif_file: semgrep.sarif | |
category: semgrep | |
typescript-strict-checks: | |
name: TypeScript Security Checks | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Bun | |
uses: oven-sh/setup-bun@v2 | |
with: | |
bun-version: '1.2.14' | |
- name: Install dependencies | |
run: bun install --frozen-lockfile | |
- name: TypeScript strict null checks | |
run: | | |
# Create a strict tsconfig for security checks | |
cat > tsconfig.strict.json << 'EOF' | |
{ | |
"extends": "./tsconfig.json", | |
"compilerOptions": { | |
"strict": true, | |
"noImplicitAny": true, | |
"strictNullChecks": true, | |
"strictFunctionTypes": true, | |
"strictBindCallApply": true, | |
"strictPropertyInitialization": true, | |
"noImplicitThis": true, | |
"alwaysStrict": true, | |
"noUnusedLocals": true, | |
"noUnusedParameters": true, | |
"noImplicitReturns": true, | |
"noFallthroughCasesInSwitch": true, | |
"noUncheckedIndexedAccess": true | |
} | |
} | |
EOF | |
# Run strict type checking | |
echo "## 🔒 TypeScript Strict Security Checks" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
if bunx tsc --project tsconfig.strict.json --noEmit; then | |
echo "✅ All TypeScript strict checks passed!" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "⚠️ Some TypeScript strict checks failed. Review the output above." >> $GITHUB_STEP_SUMMARY | |
fi | |
secrets-scan: | |
name: Secret Scanning | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Run Gitleaks | |
uses: gitleaks/gitleaks-action@v2 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Check for hardcoded secrets | |
run: | | |
echo "## 🔐 Secret Scanning Report" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Pattern checks for common secret patterns | |
PATTERNS=( | |
"password\s*=\s*[\"'][^\"']+[\"']" | |
"api[_-]?key\s*=\s*[\"'][^\"']+[\"']" | |
"token\s*=\s*[\"'][^\"']+[\"']" | |
"secret\s*=\s*[\"'][^\"']+[\"']" | |
"private[_-]?key" | |
) | |
FOUND_ISSUES=false | |
for pattern in "${PATTERNS[@]}"; do | |
if grep -r -i -E "$pattern" --include="*.ts" --include="*.js" --include="*.json" --exclude-dir=node_modules --exclude-dir=dist . > /dev/null 2>&1; then | |
echo "⚠️ Potential secrets found matching pattern: $pattern" >> $GITHUB_STEP_SUMMARY | |
FOUND_ISSUES=true | |
fi | |
done | |
if [ "$FOUND_ISSUES" = false ]; then | |
echo "✅ No hardcoded secrets detected!" >> $GITHUB_STEP_SUMMARY | |
fi | |
dependency-review: | |
name: Dependency Review | |
runs-on: ubuntu-latest | |
if: github.event_name == 'pull_request' | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Dependency Review | |
uses: actions/dependency-review-action@v4 | |
with: | |
fail-on-severity: moderate | |
deny-licenses: GPL-3.0, AGPL-3.0, LGPL-3.0 | |
comment-summary-in-pr: always | |
security-report: | |
name: Security Report Summary | |
runs-on: ubuntu-latest | |
needs: [dependency-audit, license-check, semgrep, typescript-strict-checks, secrets-scan] | |
if: always() | |
steps: | |
- name: Generate Security Summary | |
run: | | |
echo "# 🛡️ Security Scan Summary" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY | |
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
echo "| Dependency Audit | ${{ needs.dependency-audit.result }} |" >> $GITHUB_STEP_SUMMARY | |
echo "| License Compliance | ${{ needs.license-check.result }} |" >> $GITHUB_STEP_SUMMARY | |
echo "| Semgrep SAST | ${{ needs.semgrep.result }} |" >> $GITHUB_STEP_SUMMARY | |
echo "| TypeScript Checks | ${{ needs.typescript-strict-checks.result }} |" >> $GITHUB_STEP_SUMMARY | |
echo "| Secrets Scan | ${{ needs.secrets-scan.result }} |" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
if [ "${{ needs.dependency-audit.result }}" != "success" ] || \ | |
[ "${{ needs.license-check.result }}" != "success" ] || \ | |
[ "${{ needs.semgrep.result }}" != "success" ] || \ | |
[ "${{ needs.typescript-strict-checks.result }}" != "success" ] || \ | |
[ "${{ needs.secrets-scan.result }}" != "success" ]; then | |
echo "⚠️ **Some security checks require attention**" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "✅ **All security checks passed!**" >> $GITHUB_STEP_SUMMARY | |
fi |