Skip to content

Security Compliance Scanning #40

Security Compliance Scanning

Security Compliance Scanning #40

name: Security Compliance Scanning
on:
pull_request:
branches: [main, staging]
push:
branches: [main]
schedule:
# Daily scan at 02:00 UTC to catch new advisories published between code changes
- cron: '0 2 * * *'
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
# ---------------------------------------------------------------------------
# 1. Dependency vulnerability scanning (cargo audit)
# ---------------------------------------------------------------------------
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Run cargo audit
id: cargo_audit
run: |
cargo audit --json > audit-report.json 2>&1 || true
# Exit non-zero if any critical or high severity vulnerabilities found
# that are not in the allowlist
cargo audit --deny warnings --deny unsound 2>&1
continue-on-error: true
- name: Check for critical/high vulnerabilities (fail CI)
run: |
if [ -f audit-report.json ]; then
CRITICAL=$(cat audit-report.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
vulns = data.get('vulnerabilities', {}).get('list', [])
critical_high = [v for v in vulns if v.get('advisory', {}).get('cvss', {}).get('score', 0) >= 7.0]
print(len(critical_high))
" 2>/dev/null || echo "0")
if [ "$CRITICAL" -gt "0" ]; then
echo "::error::Found $CRITICAL critical/high severity dependency vulnerabilities. CI build failed."
exit 1
fi
fi
- name: Upload dependency scan report
if: always()
uses: actions/upload-artifact@v4
with:
name: dependency-scan-report-${{ github.sha }}
path: audit-report.json
retention-days: 90
- name: Alert on new critical advisory (scheduled scan)
if: github.event_name == 'schedule' && failure()
run: |
echo "::error::Daily dependency scan detected new critical vulnerability advisory. Security team must be notified immediately."
# In production: POST to security alerting webhook
# curl -X POST "$SECURITY_ALERT_WEBHOOK" \
# -H "Content-Type: application/json" \
# -d '{"event":"new_critical_advisory","scan":"cargo_audit","sha":"${{ github.sha }}"}'
# ---------------------------------------------------------------------------
# 2. Static Application Security Testing (SAST)
# ---------------------------------------------------------------------------
sast:
name: Static Application Security Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain with clippy
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Run security-focused clippy lints
run: |
cargo clippy \
--all-targets \
--all-features \
-- \
-D warnings \
-D clippy::all \
-W clippy::pedantic \
-D clippy::unwrap_used \
-D clippy::expect_used \
-D clippy::panic \
-D clippy::integer_arithmetic \
-D clippy::as_conversions \
-D clippy::indexing_slicing \
-W clippy::suspicious \
-W clippy::correctness \
2>&1 | tee sast-report.txt
- name: Scan for unsafe Rust usage
run: |
# Detect unsafe blocks without justification comments
UNSAFE_COUNT=$(grep -rn "unsafe {" src/ --include="*.rs" | grep -v "// SAFETY:" | wc -l || echo "0")
if [ "$UNSAFE_COUNT" -gt "0" ]; then
echo "::warning::Found $UNSAFE_COUNT unsafe blocks without SAFETY justification comments"
grep -rn "unsafe {" src/ --include="*.rs" | grep -v "// SAFETY:"
fi
- name: Scan for hardcoded secrets patterns
run: |
# Basic pattern scan — gitleaks handles the full scan below
PATTERNS=(
"password\s*=\s*['\"][^'\"]{8,}"
"secret\s*=\s*['\"][^'\"]{8,}"
"api_key\s*=\s*['\"][^'\"]{8,}"
"private_key\s*=\s*['\"][^'\"]{8,}"
)
FOUND=0
for pattern in "${PATTERNS[@]}"; do
if grep -rniE "$pattern" src/ --include="*.rs" 2>/dev/null; then
FOUND=1
fi
done
if [ "$FOUND" -eq "1" ]; then
echo "::error::Potential hardcoded secrets detected in source code"
exit 1
fi
- name: Upload SAST report
if: always()
uses: actions/upload-artifact@v4
with:
name: sast-report-${{ github.sha }}
path: sast-report.txt
retention-days: 90
# ---------------------------------------------------------------------------
# 3. Secrets detection (gitleaks)
# ---------------------------------------------------------------------------
secrets-detection:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for first-time git history scan
- name: Run gitleaks on full git history
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
with:
args: detect --source . --log-opts="--all" --report-format json --report-path gitleaks-report.json
- name: Upload secrets scan report
if: always()
uses: actions/upload-artifact@v4
with:
name: secrets-scan-report-${{ github.sha }}
path: gitleaks-report.json
retention-days: 90
- name: Alert on detected secrets
if: failure()
run: |
echo "::error::Secrets detected in repository. Rotate all exposed credentials immediately and purge git history."
# ---------------------------------------------------------------------------
# 4. Container image vulnerability scanning (Trivy)
# ---------------------------------------------------------------------------
container-scan:
name: Container Image Vulnerability Scan
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t aframp-backend:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: aframp-backend:${{ github.sha }}
format: json
output: trivy-report.json
severity: CRITICAL,HIGH,MEDIUM,LOW
exit-code: 0 # We handle exit code manually below
- name: Fail on critical/high CVEs
run: |
CRITICAL=$(cat trivy-report.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
count = 0
for result in data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
if vuln.get('Severity') in ('CRITICAL', 'HIGH'):
count += 1
print(count)
" 2>/dev/null || echo "0")
if [ "$CRITICAL" -gt "0" ]; then
echo "::error::Found $CRITICAL critical/high CVEs in container image. Image push blocked."
exit 1
fi
- name: Upload container scan report
if: always()
uses: actions/upload-artifact@v4
with:
name: container-scan-report-${{ github.sha }}
path: trivy-report.json
retention-days: 90
- name: Push image to registry (only if scan passed)
if: success() && github.ref == 'refs/heads/main'
run: |
echo "Container scan passed — image is safe to push to registry"
# docker tag aframp-backend:${{ github.sha }} ${{ secrets.REGISTRY }}/aframp-backend:${{ github.sha }}
# docker push ${{ secrets.REGISTRY }}/aframp-backend:${{ github.sha }}
# ---------------------------------------------------------------------------
# 5. Weekly production image rescan
# ---------------------------------------------------------------------------
production-image-rescan:
name: Weekly Production Image Rescan
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Pull current production image
run: |
echo "Pulling production image for rescan..."
# docker pull ${{ secrets.REGISTRY }}/aframp-backend:production
- name: Rescan production image against updated CVE database
run: |
echo "Rescanning production image with latest CVE database..."
# trivy image --severity CRITICAL,HIGH --format json \
# --output production-rescan-report.json \
# ${{ secrets.REGISTRY }}/aframp-backend:production
- name: Alert on new critical CVEs in production image
if: failure()
run: |
echo "::error::New critical CVE detected in currently deployed production image. Immediate patching required."
# ---------------------------------------------------------------------------
# 6. Ingest findings into vulnerability management system
# ---------------------------------------------------------------------------
ingest-findings:
name: Ingest Scan Findings
runs-on: ubuntu-latest
needs: [dependency-scan, sast, secrets-detection]
if: always() && github.ref == 'refs/heads/main'
steps:
- name: Download all scan reports
uses: actions/download-artifact@v4
- name: Ingest findings via API
run: |
echo "Ingesting scan findings into vulnerability management system..."
# In production, parse each report and POST to:
# POST /api/admin/security/findings/ingest
# with appropriate severity, source, and raw_finding payload