name: Security

on:
  schedule:
    - cron: '0 2 * * 1'  # Weekly on Monday at 2 AM UTC
  push:
    branches: [master]
  pull_request:
    branches: [master]
    # Only run security audit when dependency files change
    paths:
      - '**/Cargo.toml'
      - '**/Cargo.lock'
      - 'deny.toml'
  workflow_dispatch:

# Cancel in-progress runs when new commits are pushed
concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

permissions:
  contents: read

env:
  CARGO_PROFILE_DEV_DEBUG: line-tables-only

jobs:
  audit:
    name: Security Audit
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6

    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable

    - name: Cache Rust dependencies
      uses: Swatinem/rust-cache@v2
      with:
        cache-bin: "false"

    - name: Install cargo-audit
      uses: taiki-e/install-action@v2
      with:
        tool: cargo-audit

    - name: Run security audit
      run: cargo audit --json > audit-results.json || true

    - name: Display audit results
      run: |
        if [ -f audit-results.json ]; then
          echo "Security audit results:"
          cat audit-results.json | jq -r '.vulnerabilities[] | "\(.advisory.id): \(.advisory.title) (\(.advisory.severity))"' || echo "No vulnerabilities found"
        fi

    - name: Upload audit results
      uses: actions/upload-artifact@v7
      if: always()
      with:
        name: security-audit-results
        path: audit-results.json

  dependency-check:
    name: Dependency Check
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6

    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable

    - name: Cache Rust dependencies
      uses: Swatinem/rust-cache@v2
      with:
        cache-bin: "false"

    - name: Install cargo-deny
      uses: taiki-e/install-action@v2
      with:
        tool: cargo-deny

    - name: Create deny.toml if not exists
      run: |
        if [ ! -f deny.toml ]; then
          cat > deny.toml << 'EOF'
        [bans]
        multiple-versions = "warn"
        wildcards = "deny"

        [licenses]
        copyleft = "deny"
        allow-osi-fsf-free = "neither"
        default = "deny"
        allow = [
            "Apache-2.0",
            "MIT",
            "BSD-2-Clause",
            "BSD-3-Clause",
            "ISC",
            "Unicode-DFS-2016",
        ]

        [advisories]
        vulnerability = "deny"
        unmaintained = "warn"
        yanked = "warn"
        notice = "warn"

        [sources]
        unknown-registry = "warn"
        unknown-git = "warn"
        EOF
        fi

    - name: Run cargo deny
      run: cargo deny check --hide-inclusion-graph

    - name: Install cargo-machete
      uses: taiki-e/install-action@v2
      with:
        tool: cargo-machete

    - name: Check for unused dependencies
      run: cargo machete --with-metadata || true

  supply-chain:
    name: Supply Chain Security
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6

    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable

    - name: Cache Rust dependencies
      uses: Swatinem/rust-cache@v2
      with:
        cache-bin: "false"

    - name: Check for suspicious dependencies
      run: |
        echo "Checking for suspicious dependencies..."
        cargo tree --duplicates || true

        echo "Checking for potential typosquatting..."
        cargo tree --format "{p}" | grep -E "(reqwest|serde|tokio|clap)" | sort | uniq -c | sort -nr

    - name: Verify Cargo.lock is valid
      run: |
        # Verify lockfile is consistent with Cargo.toml (not necessarily latest versions)
        cargo check --locked --quiet
        echo "Cargo.lock is valid and consistent with Cargo.toml"

    - name: Check for overly broad feature flags
      run: |
        echo "Checking for overly broad feature flags..."
        grep -n "features.*=.*\[.*\]" Cargo.toml || true
