release: v0.13.0 terminal usability and integration fixes (#600) #1249
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }}-${{ runner.arch }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-${{ runner.arch }}-cargo-clippy- | |
| - run: cargo clippy --locked --all-targets --all-features -- -D warnings | |
| test: | |
| name: Test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| 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 }}-${{ runner.arch }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-${{ runner.arch }}-cargo-test- | |
| - run: cargo test --locked --all-features | |
| - name: Run sealed 0research negative controls | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| cargo build --bin foxguard | |
| ref=$(python3 benchmarks/fixed-negative-controls-v2/fixed_controls.py run \ | |
| --candidate-id "ci-${GITHUB_SHA}" \ | |
| --champion-binary target/debug/foxguard \ | |
| --challenger-binary target/debug/foxguard \ | |
| --champion-source-root . \ | |
| --challenger-source-root . \ | |
| --results-dir /tmp/foxguard-negative-controls/raw \ | |
| --output /tmp/foxguard-negative-controls/evidence.json) | |
| python3 benchmarks/fixed-negative-controls-v2/fixed_controls.py verify \ | |
| --artifact /tmp/foxguard-negative-controls/evidence.json \ | |
| --ref "$ref" | |
| 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 | |
| # Pin Semgrep to a known version so parity-test drift comes from foxguard changes, not upstream behavior changes. Bump deliberately via PR + parity-test run. | |
| run: pip install semgrep==1.163.0 | |
| - name: Run Semgrep parity suite (Python) | |
| run: cargo test --test semgrep_parity | |
| - name: Run Semgrep parity suite (JavaScript) | |
| run: cargo test --test semgrep_parity_javascript | |
| - name: Run Semgrep parity suite (Go) | |
| run: cargo test --test semgrep_parity_go | |
| - name: Run Semgrep parity suite (Java) | |
| run: cargo test --test semgrep_parity_java | |
| - name: Run Semgrep parity suite (C) | |
| run: cargo test --test semgrep_parity_c | |
| - name: Run Semgrep parity suite (Kotlin) | |
| run: cargo test --test semgrep_parity_kotlin | |
| - name: Run Semgrep parity suite (Haskell) | |
| run: cargo test --test semgrep_parity_haskell | |
| - name: Run Semgrep parity suite (inverse / unsupported-skip) | |
| run: cargo test --test semgrep_parity_inverse | |
| 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 test | |
| 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 test | |
| - 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: python3 action/test_count_terminal_findings.py | |
| - run: bash -n benchmarks/precision/run.sh | |
| - run: bash -n benchmarks/run.sh | |
| - run: bash -n scripts/linux-codeql-dirty-frag.sh | |
| - run: bash -n scripts/smoke-github-app-image.sh | |
| - run: bash -n plugins/claude-code/scripts/finding-state.sh | |
| - run: perl -MFcntl=:flock -e 'exit !eval { defined(Fcntl::O_NOFOLLOW()) && LOCK_EX }' | |
| - run: perl -c plugins/claude-code/scripts/with-state-lock.pl | |
| - run: bash -n plugins/claude-code/scripts/restore-unresolved-findings.sh | |
| - run: bash -n plugins/claude-code/scripts/scan-edited-file.sh | |
| - run: bash -n plugins/claude-code/tests/test-compaction-state.sh | |
| - run: jq empty plugins/claude-code/hooks/hooks.json | |
| - run: bash plugins/claude-code/tests/test-compaction-state.sh | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - run: node --test scripts/verify-vscode-marketplace.test.mjs | |
| - run: bash -n scripts/release.sh | |
| action-outputs: | |
| name: Action Outputs | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| BADGE_LABEL: 'audit "quoted" \ label' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Create clean scan fixture | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/action-fixture" | |
| printf 'print("safe")\n' > "$RUNNER_TEMP/action-fixture/safe.py" | |
| - name: Scan through the composite action | |
| id: scan | |
| uses: ./action | |
| with: | |
| path: ${{ runner.temp }}/action-fixture | |
| version: v0.12.0 | |
| upload-sarif: "false" | |
| badge-label: ${{ env.BADGE_LABEL }} | |
| - name: Consume public action outputs | |
| env: | |
| FINDINGS_COUNT: ${{ steps.scan.outputs.findings-count }} | |
| SCAN_REPORT: ${{ steps.scan.outputs.sarif-file }} | |
| BADGE_REPORT: ${{ steps.scan.outputs.badge-json }} | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| assert os.environ["FINDINGS_COUNT"] == "0", "missing or incorrect findings-count output" | |
| with open(os.environ["SCAN_REPORT"]) as report_file: | |
| report = json.load(report_file) | |
| assert report["runs"][0]["results"] == [] | |
| with open(os.environ["BADGE_REPORT"]) as badge_file: | |
| badge = json.load(badge_file) | |
| assert badge["label"] == os.environ["BADGE_LABEL"] | |
| assert badge["message"] == "clean" | |
| PY | |
| precision-corpus-metadata: | |
| name: Precision Corpus Metadata | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 | |
| with: | |
| python-version: "3.12" | |
| - run: python3 benchmarks/precision/precision.py validate | |
| - run: python3 -m unittest benchmarks/precision/test_zero_research.py benchmarks/precision/test_fixed_cases_v2.py | |
| - run: python3 -m unittest discover -s benchmarks/fixed-negative-controls-v2 -p 'test_*.py' | |
| - run: python3 -m unittest discover -s benchmarks/held-out-capability-v1 -p 'test_*.py' | |
| readme-version-refs: | |
| name: README Version Refs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Check README install refs match Cargo.toml | |
| run: | | |
| cargo_v=$(grep '^version' Cargo.toml | head -1 | sed -E 's/version = "(.+)"/\1/') | |
| action_v=$(grep -oE '0sec-labs/foxguard/action@v[0-9]+\.[0-9]+\.[0-9]+' README.md | head -1 | sed -E 's/.*v(.+)/\1/') | |
| rev_v=$(grep -oE 'rev:\s+v[0-9]+\.[0-9]+\.[0-9]+' README.md | head -1 | sed -E 's/.*v(.+)/\1/') | |
| fail=0 | |
| if [ "$action_v" != "$cargo_v" ]; then | |
| echo "::error::action ref v$action_v != Cargo v$cargo_v" | |
| fail=1 | |
| fi | |
| if [ "$rev_v" != "$cargo_v" ]; then | |
| echo "::error::pre-commit rev v$rev_v != Cargo v$cargo_v" | |
| fail=1 | |
| fi | |
| exit $fail | |
| 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 }}-${{ runner.arch }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-${{ runner.arch }}-cargo-build- | |
| - run: cargo build --release --all-features | |
| linux-compat: | |
| name: Linux glibc compat | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c | |
| with: | |
| toolchain: "1.88.0" | |
| targets: x86_64-unknown-linux-musl | |
| - name: Install musl tools | |
| # Hosted runners include apt metadata; avoid an unbounded mirror refresh. | |
| run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends musl-tools | |
| - uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-musl-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-musl-cargo- | |
| - name: Build static musl binary | |
| run: cargo build --release --target x86_64-unknown-linux-musl | |
| - name: Verify binary is static | |
| run: | | |
| file target/x86_64-unknown-linux-musl/release/foxguard | |
| ldd target/x86_64-unknown-linux-musl/release/foxguard 2>&1 | grep -q "not a dynamic executable" || ldd target/x86_64-unknown-linux-musl/release/foxguard 2>&1 | grep -q "statically linked" | |
| - name: Test binary across Linux distros | |
| run: | | |
| binary="target/x86_64-unknown-linux-musl/release/foxguard" | |
| for image in ubuntu:20.04 ubuntu:22.04 debian:11 debian:12 fedora:38 alpine:3.18 amazonlinux:2023; do | |
| echo "--- Testing on $image ---" | |
| docker run --rm -v "$PWD:/work:ro" "$image" /work/$binary --version | |
| done | |
| 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 }}-${{ runner.arch }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-${{ runner.arch }}-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: | | |
| # Bypass .foxguard.yml so the repo baseline does not suppress fixture findings. | |
| if ./target/release/foxguard --config /dev/null 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 |