Skip to content

release: v0.13.0 terminal usability and integration fixes (#600) #1249

release: v0.13.0 terminal usability and integration fixes (#600)

release: v0.13.0 terminal usability and integration fixes (#600) #1249

Workflow file for this run

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