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@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 - name: Set up Python continue-on-error: true uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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: | # archive/v1 is frozen research code and is not shipped. Scan the # maintained Python packages and operator scripts instead. # Keep the Security tab actionable: publish high-severity findings. # Medium/low findings are reviewed during focused local audits. bandit -lll -r python/ scripts/ firmware/esp32-csi-node/ aether-arena/ \ -x '*/tests/*,*/test/*,*/test_*.py,*/bench/*' \ -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@a2983b8bed1923f44751c5c43237f479442827b3 # 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 \ --severity=ERROR \ --exclude='**/tests/**' --exclude='**/test/**' \ --exclude='**/test_*.py' --exclude='**/bench/**' \ --sarif --output=semgrep.sarif \ python/ scripts/ firmware/esp32-csi-node/ aether-arena/ continue-on-error: true - name: Upload Semgrep results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # 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@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Python continue-on-error: true uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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@a2983b8bed1923f44751c5c43237f479442827b3 # v3 if: always() with: sarif_file: snyk-results.sarif category: snyk - name: Upload vulnerability reports continue-on-error: true uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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 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@11d5960a326750d5838078e36cf38b85af677262 # v4 with: submodules: recursive - name: Set up Docker Buildx continue-on-error: true uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Build Docker image for scanning continue-on-error: true uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: . file: docker/Dockerfile.rust 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' severity: 'CRITICAL,HIGH' ignore-unfixed: true limit-severities-for-sarif: true - name: Upload Trivy results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3 if: always() with: sarif_file: 'trivy-results.sarif' category: trivy # Trivy is the single container SARIF authority. Grype and Docker Scout # produced duplicate alerts for the same image packages and obscured the # actionable high/critical findings. # 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@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Run KICS IaC scan continue-on-error: true uses: checkmarx/kics-github-action@05aa5eb70eede1355220f4ca5238d96b397e30a6 # v2.1.20 with: # Scan RuView-owned operational IaC only. Submodules are audited and # fixed in their owning repositories; archived/benchmark fixtures are # intentionally not production infrastructure. path: '.github/workflows,docker,logging,v2/crates/nvsim-server/Dockerfile' output_path: kics-results output_formats: 'sarif' exclude_paths: '.git,node_modules' exclude_queries: 'a7ef1e8c-fbf8-4ac1-b8c7-2c3b0e6c6c6c' exclude_severities: 'info' - name: Upload KICS results to GitHub Security continue-on-error: true uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # 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@11d5960a326750d5838078e36cf38b85af677262 # 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@dcedce43c6f43de0b836d1fe38946645c9c638dc # 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@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Python continue-on-error: true uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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@11d5960a326750d5838078e36cf38b85af677262 # 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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e # 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@00f12e3e20659f42342b1c0226afda7f7c042325 # v6 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'] })