foxguard 0.6.1

Security scanner as fast as a linter. 170+ built-in rules, 10 languages, sub-second scans.
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  NODE_VERSION: "22.12.0"

jobs:
  fmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - run: cargo fmt --check

  clippy:
    name: Clippy
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-clippy-
      - run: cargo clippy -- -D warnings

  test:
    name: Test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-test-
      - run: cargo test

  rule-inventory-check:
    name: Rule Inventory
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-gen-rules-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-gen-rules-
      - name: Regenerate rules.ts
        run: cargo run --quiet --bin gen_rules_ts > /tmp/rules.ts.generated
      - name: Diff against committed file
        run: |
          if ! diff -u www/src/data/rules.ts /tmp/rules.ts.generated; then
            echo "::error::www/src/data/rules.ts is out of sync with the Rust rule registry."
            echo "Regenerate with: cargo run --bin gen_rules_ts > www/src/data/rules.ts"
            exit 1
          fi

  semgrep-parity:
    name: Semgrep Parity
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v6
        with:
          python-version: "3.12"
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ubuntu-cargo-semgrep-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ubuntu-cargo-semgrep-
      - name: Install semgrep
        run: pip install semgrep
      - name: Run Semgrep parity suite
        run: cargo test --test semgrep_parity

  website:
    name: Website Build
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: www
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm
          cache-dependency-path: www/package-lock.json
      - run: npm ci
      - run: npm run build

  vscode-extension:
    name: VS Code Extension
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: vscode-extension
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm
          cache-dependency-path: vscode-extension/package-lock.json
      - run: npm ci
      - run: npm run compile

  npm-package:
    name: npm Package
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: packages/npm
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: npm pack --dry-run

  scripts:
    name: Script Syntax
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: bash -n action/entrypoint.sh
      - run: bash -n benchmarks/run.sh
      - run: bash -n scripts/release.sh

  build:
    name: Build Release
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-build-
      - run: cargo build --release

  dogfood:
    name: Dogfood (foxguard scans itself)
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-build-

      - name: Build foxguard
        run: cargo build --release

      - name: Scan own source (must be clean)
        run: ./target/release/foxguard --severity high src/

      - name: Scan own source for secrets (must be clean)
        run: ./target/release/foxguard secrets src/

      - name: Verify fixture detection (must find issues)
        run: |
          if ./target/release/foxguard tests/fixtures/ 2>&1; then
            echo "::error::Expected foxguard to find issues in test fixtures but it exited clean"
            exit 1
          else
            echo "Fixtures correctly detected — foxguard found issues as expected"
          fi