Skip to content

security(deps)(deps): bump the nodejs-minor-patch group across 1 directory with 8 updates #2379

security(deps)(deps): bump the nodejs-minor-patch group across 1 directory with 8 updates

security(deps)(deps): bump the nodejs-minor-patch group across 1 directory with 8 updates #2379

Workflow file for this run

name: Security Checks
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# Run security checks daily at 2 AM UTC
- cron: '0 2 * * *'
permissions:
contents: read
security-events: write
actions: read
pull-requests: write # Required for commenting on PRs
jobs:
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
# Python dependency scanning with Safety
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install safety bandit semgrep
pip install -e ".[dev]"
- name: Python Safety Check
continue-on-error: true
run: |
timeout 120 safety check --json --output safety-report.json || true
if [ -f safety-report.json ]; then
echo "Safety vulnerabilities found. Check the report:"
cat safety-report.json
fi
# Node.js dependency scanning with npm audit
- name: Install Node.js dependencies
run: |
cd frontend
npm ci
- name: Node.js Audit
run: |
cd frontend
npm audit --audit-level moderate --json > npm-audit-report.json || true
if [ -s npm-audit-report.json ]; then
echo "NPM vulnerabilities found. Check the report:"
cat npm-audit-report.json
fi
# Advanced dependency scanning with Snyk (requires SNYK_TOKEN)
# - name: Run Snyk to check for vulnerabilities
# uses: snyk/actions/setup@master
#
# - name: Snyk Python scan
# run: |
# snyk test --file=pyproject.toml --json > snyk-python-report.json || true
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# continue-on-error: true
# - name: Snyk Node.js scan
# run: |
# cd frontend
# snyk test --json > snyk-node-report.json || true
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# continue-on-error: true
# Upload dependency scan artifacts
- name: Upload dependency scan reports
uses: actions/upload-artifact@v7
if: always()
with:
name: dependency-scan-reports
path: |
safety-report.json
frontend/npm-audit-report.json
sast-scan:
name: Static Application Security Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
# Bandit for Python SAST
- name: Install Bandit
run: pip install bandit[toml]
- name: Run Bandit Python SAST
run: |
bandit -r . -f json -o bandit-report.json || true
if [ -f bandit-report.json ]; then
echo "Bandit found security issues:"
cat bandit-report.json
fi
# Semgrep for multi-language SAST (requires SEMGREP_APP_TOKEN)
# - name: Run Semgrep
# uses: returntocorp/semgrep-action@v1
# with:
# config: >-
# p/security-audit
# p/secrets
# p/python
# p/javascript
# p/typescript
# p/react
# generateSarif: "1"
# env:
# SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
# CodeQL Analysis
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript, python
queries: security-extended,security-and-quality
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript,python"
# Upload SAST artifacts
- name: Upload SAST reports
uses: actions/upload-artifact@v7
if: always()
with:
name: sast-reports
path: |
bandit-report.json
secrets-scan:
name: Secrets Scanning
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0 # Full history for better secret detection
# TruffleHog for secrets scanning
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
extra_args: --debug --only-verified
# GitLeaks for additional secret detection (requires GITLEAKS_LICENSE)
# - name: Run GitLeaks
# uses: gitleaks/gitleaks-action@v2
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
container-scan:
name: Container Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Build Docker image for scanning
run: |
docker build -t security-scan:latest .
# Trivy for container vulnerability scanning
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'security-scan:latest'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
# Docker Scout for additional container scanning
- name: Docker Scout scan
uses: docker/scout-action@v1
if: always()
continue-on-error: true
with:
command: cves
image: security-scan:latest
only-severities: critical,high
format: sarif
output: scout-results.sarif
- name: Upload Scout scan results
uses: github/codeql-action/upload-sarif@v3
if: always() && hashFiles('scout-results.sarif') != ''
with:
sarif_file: 'scout-results.sarif'
# SBOM generation with Syft (Anchore) — bundled into the container scan artifact
- name: Generate SBOM (Syft)
uses: anchore/sbom-action@v0
with:
image: security-scan:latest
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
# Grype for image vulnerability scanning. Report-first defaults: fail-build=false,
# only-fixed=true so signal stays focused on actionable, fixable CVEs.
- name: Run Grype vulnerability scanner
id: grype
uses: anchore/scan-action@v7
continue-on-error: true
with:
image: security-scan:latest
fail-build: false
severity-cutoff: high
only-fixed: true
output-format: sarif
output-file: grype-results.sarif
- name: Upload Grype scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always() && hashFiles('grype-results.sarif') != ''
with:
sarif_file: grype-results.sarif
category: grype
# Upload container scan artifacts
- name: Upload container scan reports
uses: actions/upload-artifact@v7
if: always()
with:
name: container-scan-reports
path: |
trivy-results.sarif
scout-results.sarif
grype-results.sarif
sbom.spdx.json
if-no-files-found: ignore
security-summary:
name: Security Summary
runs-on: ubuntu-latest
needs: [dependency-scan, sast-scan, secrets-scan, container-scan]
if: always()
steps:
- name: Download all artifacts
uses: actions/download-artifact@v8
- name: Install jq
run: sudo apt-get install -y jq
- name: Generate Security Summary
run: |
echo "# Security Scan Summary" > security-summary.md
echo "" >> security-summary.md
# --- Python Dependencies (Safety) ---
echo "### Python Dependencies (Safety)" >> security-summary.md
REPORT="dependency-scan-reports/safety-report.json"
if [ -f "$REPORT" ] && [ -s "$REPORT" ]; then
VULN_COUNT=$(jq '[.vulnerabilities // [] | length] | add // 0' "$REPORT" 2>/dev/null || echo "0")
if [ "$VULN_COUNT" -gt 0 ] 2>/dev/null; then
echo "**$VULN_COUNT vulnerabilities found**" >> security-summary.md
echo "" >> security-summary.md
echo "| Package | Installed | Vuln ID | Severity | Description |" >> security-summary.md
echo "|---------|-----------|---------|----------|-------------|" >> security-summary.md
jq -r '.vulnerabilities[]? | "| \(.package_name // "?") | \(.analyzed_version // "?") | \(.vulnerability_id // "?") | \(.severity // "?") | \(.advisory[0:80] // "?") |"' "$REPORT" 2>/dev/null >> security-summary.md || true
else
echo "No known vulnerabilities found." >> security-summary.md
fi
else
echo "Report not available." >> security-summary.md
fi
echo "" >> security-summary.md
# --- Node.js Dependencies (npm audit) ---
echo "### Node.js Dependencies (npm audit)" >> security-summary.md
REPORT="dependency-scan-reports/npm-audit-report.json"
if [ -f "$REPORT" ] && [ -s "$REPORT" ]; then
TOTAL=$(jq '.metadata.vulnerabilities // {} | to_entries | map(.value) | add // 0' "$REPORT" 2>/dev/null || echo "0")
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
CRIT=$(jq '.metadata.vulnerabilities.critical // 0' "$REPORT" 2>/dev/null)
HIGH=$(jq '.metadata.vulnerabilities.high // 0' "$REPORT" 2>/dev/null)
MOD=$(jq '.metadata.vulnerabilities.moderate // 0' "$REPORT" 2>/dev/null)
LOW=$(jq '.metadata.vulnerabilities.low // 0' "$REPORT" 2>/dev/null)
echo "**$TOTAL vulnerabilities:** $CRIT critical, $HIGH high, $MOD moderate, $LOW low" >> security-summary.md
echo "" >> security-summary.md
# List top-level vulnerable packages
echo "| Package | Severity | Via | Fix Available |" >> security-summary.md
echo "|---------|----------|----|---------------|" >> security-summary.md
jq -r '.vulnerabilities // {} | to_entries[] | "| \(.key) | \(.value.severity // "?") | \(.value.via | if type == "array" then [.[] | if type == "string" then . else .name // "?" end] | join(", ") else "?" end) | \(if .value.fixAvailable then "Yes" else "No" end) |"' "$REPORT" 2>/dev/null | head -20 >> security-summary.md || true
else
echo "No vulnerabilities found." >> security-summary.md
fi
else
echo "Report not available." >> security-summary.md
fi
echo "" >> security-summary.md
# --- Python SAST (Bandit) ---
echo "### Python SAST (Bandit)" >> security-summary.md
REPORT="sast-reports/bandit-report.json"
if [ -f "$REPORT" ] && [ -s "$REPORT" ]; then
ISSUE_COUNT=$(jq '.results | length' "$REPORT" 2>/dev/null || echo "0")
if [ "$ISSUE_COUNT" -gt 0 ] 2>/dev/null; then
HIGH_SEV=$(jq '[.results[] | select(.issue_severity == "HIGH")] | length' "$REPORT" 2>/dev/null || echo "0")
MED_SEV=$(jq '[.results[] | select(.issue_severity == "MEDIUM")] | length' "$REPORT" 2>/dev/null || echo "0")
LOW_SEV=$(jq '[.results[] | select(.issue_severity == "LOW")] | length' "$REPORT" 2>/dev/null || echo "0")
echo "**$ISSUE_COUNT issues:** $HIGH_SEV high, $MED_SEV medium, $LOW_SEV low severity" >> security-summary.md
echo "" >> security-summary.md
# Show high/medium issues with file locations
HIGH_MED=$(jq '[.results[] | select(.issue_severity == "HIGH" or .issue_severity == "MEDIUM")] | length' "$REPORT" 2>/dev/null || echo "0")
if [ "$HIGH_MED" -gt 0 ] 2>/dev/null; then
echo "**High/Medium severity issues:**" >> security-summary.md
echo "" >> security-summary.md
echo "| Severity | Confidence | File | Line | Issue |" >> security-summary.md
echo "|----------|------------|------|------|-------|" >> security-summary.md
jq -r '.results[] | select(.issue_severity == "HIGH" or .issue_severity == "MEDIUM") | "| \(.issue_severity) | \(.issue_confidence) | \(.filename | split("/") | .[-2:] | join("/")) | \(.line_number) | \(.issue_text[0:80]) |"' "$REPORT" 2>/dev/null | head -30 >> security-summary.md || true
fi
else
echo "No security issues found." >> security-summary.md
fi
else
echo "Report not available." >> security-summary.md
fi
echo "" >> security-summary.md
# --- Job Status ---
echo "### Scan Status" >> security-summary.md
echo "| Scan | Status |" >> security-summary.md
echo "|------|--------|" >> security-summary.md
echo "| Dependency Scan | ${{ needs.dependency-scan.result }} |" >> security-summary.md
echo "| SAST Scan | ${{ needs.sast-scan.result }} |" >> security-summary.md
echo "| Secrets Scan | ${{ needs.secrets-scan.result }} |" >> security-summary.md
echo "| Container Scan | ${{ needs.container-scan.result }} |" >> security-summary.md
cat security-summary.md
- name: Upload Security Summary
uses: actions/upload-artifact@v7
with:
name: security-summary
path: security-summary.md
# Comment on PR with security summary
- name: Comment PR with Security Summary
if: github.event_name == 'pull_request'
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync('security-summary.md', 'utf8');
// Find and update existing security comment, or create a new one
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const marker = '<!-- security-scan-summary -->';
const body = `${marker}\n## Security Scan Results\n\n${summary}`;
const existing = comments.data.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body,
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body,
});
}