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: |
        bandit -r 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

    - name: Run Semgrep security scan
      continue-on-error: true
      uses: returntocorp/semgrep-action@v1
      with:
        config: >-
          p/security-audit
          p/secrets
          p/python
          p/docker
          p/kubernetes
      env:
        SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
        
    - name: Generate Semgrep SARIF
      run: |
        semgrep --config=p/security-audit --config=p/secrets --config=p/python --sarif --output=semgrep.sarif 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@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']
          })