name: Security Scanning on: push: branches: [ main, develop, 'feat/*' ] pull_request: branches: [ main, develop ] schedule: # Run security scans daily at 2 AM UTC - cron: '0 2 * * *' workflow_dispatch: env: PYTHON_VERSION: '3.11' jobs: # Static Application Security Testing (SAST) sast: name: Static Application Security Testing runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR permissions: security-events: write actions: read contents: read steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python continue-on-error: true uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies continue-on-error: true run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install bandit semgrep safety - name: Run Bandit security scan run: | # The Python codebase lives under archive/v1/src (it moved there when # the runtime was rewritten in Rust). Scanning `src/` matched nothing, # so this SAST step was a silent no-op. bandit -r archive/v1/src/ -f sarif -o bandit-results.sarif continue-on-error: true - name: Upload Bandit results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: bandit-results.sarif category: bandit # Removed the deprecated `returntocorp/semgrep-action@v1` step: it was # redundant (the pip `semgrep --sarif` below is what feeds GitHub Security; # the action only pushed to the Semgrep cloud app via SEMGREP_APP_TOKEN) and # it pulled `returntocorp/semgrep-agent:v1` from Docker Hub on every run, # which intermittently timed out and turned this check red. The pip semgrep # (installed above) needs no Docker pull. The action's `p/docker` + # `p/kubernetes` rulesets are folded into the command below so coverage is # preserved. - name: Run Semgrep + generate SARIF run: | semgrep \ --config=p/security-audit --config=p/secrets --config=p/python \ --config=p/docker --config=p/kubernetes \ --sarif --output=semgrep.sarif archive/v1/src/ continue-on-error: true - name: Upload Semgrep results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: semgrep.sarif category: semgrep # Dependency vulnerability scanning dependency-scan: name: Dependency Vulnerability Scan runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR permissions: security-events: write actions: read contents: read steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 - name: Set up Python continue-on-error: true uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies continue-on-error: true run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install safety pip-audit - name: Run Safety check run: | safety check --json --output safety-report.json continue-on-error: true - name: Run pip-audit run: | pip-audit --format=json --output=pip-audit-report.json continue-on-error: true - name: Run Snyk vulnerability scan uses: snyk/actions/python@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0 env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --sarif-file-output=snyk-results.sarif continue-on-error: true - name: Upload Snyk results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: snyk-results.sarif category: snyk - name: Upload vulnerability reports continue-on-error: true uses: actions/upload-artifact@v4 if: always() with: name: vulnerability-reports path: | safety-report.json pip-audit-report.json snyk-results.sarif # Container security scanning container-scan: name: Container Security Scan runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR needs: [] if: github.event_name == 'push' || github.event_name == 'schedule' permissions: security-events: write actions: read contents: read steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 - name: Set up Docker Buildx continue-on-error: true uses: docker/setup-buildx-action@v3 - name: Build Docker image for scanning continue-on-error: true uses: docker/build-push-action@v7 with: context: . target: production load: true tags: wifi-densepose:scan cache-from: type=gha cache-to: type=gha,mode=max - name: Run Trivy vulnerability scanner continue-on-error: true uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: 'wifi-densepose:scan' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif' category: trivy - name: Run Grype vulnerability scanner continue-on-error: true uses: anchore/scan-action@v7 id: grype-scan with: image: 'wifi-densepose:scan' fail-build: false severity-cutoff: high output-format: sarif - name: Upload Grype results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: ${{ steps.grype-scan.outputs.sarif }} category: grype - name: Run Docker Scout continue-on-error: true uses: docker/scout-action@v1 if: always() with: command: cves image: wifi-densepose:scan sarif-file: scout-results.sarif summary: true - name: Upload Docker Scout results continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: scout-results.sarif category: docker-scout # Infrastructure as Code security scanning iac-scan: name: Infrastructure Security Scan runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR permissions: security-events: write actions: read contents: read steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 - name: Run Checkov IaC scan continue-on-error: true uses: bridgecrewio/checkov-action@99bb2caf247dfd9f03cf984373bc6043d4e32ebf # v12.1347.0 with: directory: . framework: kubernetes,dockerfile,terraform,ansible output_format: sarif output_file_path: checkov-results.sarif quiet: true soft_fail: true - name: Upload Checkov results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: checkov-results.sarif category: checkov - name: Run Terrascan IaC scan continue-on-error: true uses: tenable/terrascan-action@3a6e87da8e244513bd77b631e624552643f794c6 # v1.4.1 with: iac_type: 'k8s' iac_version: 'v1' policy_type: 'k8s' only_warn: true sarif_upload: true - name: Run KICS IaC scan continue-on-error: true uses: checkmarx/kics-github-action@05aa5eb70eede1355220f4ca5238d96b397e30a6 # v2.1.20 with: path: '.' output_path: kics-results output_formats: 'sarif' exclude_paths: '.git,node_modules' exclude_queries: 'a7ef1e8c-fbf8-4ac1-b8c7-2c3b0e6c6c6c' - name: Upload KICS results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: kics-results/results.sarif category: kics # Secret scanning secret-scan: name: Secret Scanning runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR permissions: security-events: write actions: read contents: read steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 with: fetch-depth: 0 - name: Run TruffleHog secret scan continue-on-error: true uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 # v3.95.2 with: path: ./ base: main head: HEAD extra_args: --debug --only-verified - name: Run GitLeaks secret scan continue-on-error: true uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} - name: Run detect-secrets run: | pip install detect-secrets detect-secrets scan --all-files --baseline .secrets.baseline detect-secrets audit .secrets.baseline continue-on-error: true # License compliance scanning license-scan: name: License Compliance Scan runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 - name: Set up Python continue-on-error: true uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies continue-on-error: true run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pip-licenses licensecheck - name: Run license check continue-on-error: true run: | pip-licenses --format=json --output-file=licenses.json licensecheck --zero - name: Upload license report continue-on-error: true uses: actions/upload-artifact@v4 with: name: license-report path: licenses.json # Security policy compliance compliance-check: name: Security Policy Compliance runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR steps: - name: Checkout code continue-on-error: true uses: actions/checkout@v4 - name: Check security policy files continue-on-error: true run: | # Check for required security files files=("SECURITY.md" ".github/SECURITY.md" "docs/SECURITY.md") found=false for file in "${files[@]}"; do if [[ -f "$file" ]]; then echo "✅ Found security policy: $file" found=true break fi done if [[ "$found" == false ]]; then echo "❌ No security policy found. Please create SECURITY.md" exit 1 fi - name: Check for security headers in code continue-on-error: true run: | # Check for security-related configurations grep -r "X-Frame-Options\|X-Content-Type-Options\|X-XSS-Protection\|Content-Security-Policy" src/ || echo "⚠️ Consider adding security headers" - name: Validate Kubernetes security contexts continue-on-error: true run: | # Check for security contexts in Kubernetes manifests if [[ -d "k8s" ]]; then if find k8s/ -name "*.yaml" -exec grep -l "securityContext" {} \; | wc -l | grep -q "^0$"; then echo "⚠️ No security contexts found in Kubernetes manifests" else echo "✅ Security contexts found in Kubernetes manifests" fi else echo "ℹ️ No k8s/ directory found — skipping Kubernetes security context check" fi # Notification and reporting security-report: name: Security Report runs-on: ubuntu-latest continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR needs: [sast, dependency-scan, container-scan, iac-scan, secret-scan, license-scan, compliance-check] if: always() # Promote secret to env-scope so the gating `if:` on the Slack-notify # step below is parseable (GitHub Actions rejects `secrets.X` in # step-level `if:` expressions). env: SECURITY_SLACK_WEBHOOK_URL: ${{ secrets.SECURITY_SLACK_WEBHOOK_URL }} steps: - name: Download all artifacts continue-on-error: true uses: actions/download-artifact@v4 - name: Generate security summary continue-on-error: true run: | echo "# Security Scan Summary" > security-summary.md echo "" >> security-summary.md echo "## Scan Results" >> security-summary.md echo "- SAST: ${{ needs.sast.result }}" >> security-summary.md echo "- Dependency Scan: ${{ needs.dependency-scan.result }}" >> security-summary.md echo "- Container Scan: ${{ needs.container-scan.result }}" >> security-summary.md echo "- IaC Scan: ${{ needs.iac-scan.result }}" >> security-summary.md echo "- Secret Scan: ${{ needs.secret-scan.result }}" >> security-summary.md echo "- License Scan: ${{ needs.license-scan.result }}" >> security-summary.md echo "- Compliance Check: ${{ needs.compliance-check.result }}" >> security-summary.md echo "" >> security-summary.md echo "Generated on: $(date)" >> security-summary.md - name: Upload security summary continue-on-error: true uses: actions/upload-artifact@v4 with: name: security-summary path: security-summary.md # GitHub Actions does not allow `secrets.X` in step-level `if:` — # use env.X instead. Inherits SECURITY_SLACK_WEBHOOK_URL from the # job-level env block (added below). - name: Notify security team on critical findings continue-on-error: true if: ${{ env.SECURITY_SLACK_WEBHOOK_URL != '' && (needs.sast.result == 'failure' || needs.dependency-scan.result == 'failure' || needs.container-scan.result == 'failure') }} uses: 8398a7/action-slack@v3 with: status: failure channel: '#security' text: | 🚨 Critical security findings detected! Repository: ${{ github.repository }} Branch: ${{ github.ref }} Workflow: ${{ github.workflow }} Please review the security scan results immediately. env: SLACK_WEBHOOK_URL: ${{ env.SECURITY_SLACK_WEBHOOK_URL }} - name: Create security issue on critical findings continue-on-error: true if: needs.sast.result == 'failure' || needs.dependency-scan.result == 'failure' uses: actions/github-script@v9 with: script: | github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `Security Scan Failures - ${new Date().toISOString()}`, body: ` ## Security Scan Failures Detected **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} **Branch:** ${{ github.ref }} **Failed Scans:** - SAST: ${{ needs.sast.result }} - Dependency Scan: ${{ needs.dependency-scan.result }} - Container Scan: ${{ needs.container-scan.result }} **Action Required:** - [ ] Review security scan results - [ ] Address critical vulnerabilities - [ ] Update dependencies if needed - [ ] Re-run security scans **Security Dashboard:** Check the Security tab for detailed findings. `, labels: ['security', 'vulnerability', 'urgent'] })