From febed1e12ef03029c4627ff0fa2a7dcb59309a20 Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Thu, 12 Mar 2026 01:50:15 +0000 Subject: [PATCH] feat: add cargo-deny for supply chain safety (#834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add cargo-deny for supply chain safety Add dependency auditing via cargo-deny to catch license violations, security advisories, and untrusted sources. Integrates into CI as a parallel job alongside clippy, and into the local quality gate script. Co-Authored-By: Claude Opus 4.6 * fix: use cargo-deny action in CI, improve quality gate script - Use EmbarkStudios/cargo-deny-action@v2 instead of cargo install for faster CI execution - Fix quality_gate_strict.sh to check for cargo-deny availability instead of suppressing stderr Co-Authored-By: Claude Opus 4.6 * fix: add missing Unlicense and CDLA-Permissive-2.0 to license allowlist Add Unlicense (used by aho-corasick, memchr, etc.) and CDLA-Permissive-2.0 (used by webpki-roots) to prevent cargo deny check from failing on the current dependency tree. Co-Authored-By: Claude Opus 4.6 * chore: trigger CI after retargeting PR to staging Co-Authored-By: Claude Opus 4.6 * fix: use valid cargo-deny v0.19 syntax for unmaintained advisories The `unmaintained` field in [advisories] accepts "all", "workspace", "transitive", or "none" — not "warn". Use "workspace" to flag unmaintained direct dependencies without failing on transitive ones. Co-Authored-By: Claude Opus 4.6 * chore: re-trigger CI after adding skip-regression-check label Co-Authored-By: Claude Opus 4.6 * fix: migrate deny.toml [licenses] to version 2 format Remove deprecated `unlicensed` and `default` fields, add `version = 2`. In v2, all licenses are denied unless explicitly in the allow list, making these fields redundant. Co-Authored-By: Claude Opus 4.6 * fix: ignore pre-existing advisories in deny.toml with justification Add known RUSTSEC IDs to the ignore list so cargo-deny CI passes. Each advisory is documented with mitigation context. Dependency upgrades to resolve these should be tracked separately. Co-Authored-By: Claude Opus 4.6 * fix: address PR review feedback for cargo-deny integration - quality_gate_strict.sh: fail hard when cargo-deny is not installed instead of silently skipping, and let set -e handle check failures - deny.toml: remove empty [graph].targets so cargo-deny checks all platforms instead of only the runner's default target Co-Authored-By: Claude Opus 4.6 * fix(deny.toml): correct serde_yml advisory comment to reflect direct dependency Co-Authored-By: Claude Opus 4.6 * fix: tighten clippy-windows check in roll-up job Change from checking only `== "failure"` to checking `!= "success" && != "skipped"`. This ensures any unexpected result (e.g., cancelled) also blocks the merge, while still allowing the expected "skipped" state for non-main PRs. Addresses zmanian's review feedback on PR #834. Co-Authored-By: Claude Opus 4.6 * fix: cd to repo root in strict gate, deny wildcard versions - quality_gate_strict.sh: add `cd` to repo root so the script works when invoked from any working directory. - deny.toml: change `wildcards = "allow"` to `"deny"` to catch `*` version requirements in dependencies. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .github/workflows/code_style.yml | 21 +++++++++---- deny.toml | 50 +++++++++++++++++++++++++++++++ scripts/ci/quality_gate_strict.sh | 21 +++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 deny.toml create mode 100755 scripts/ci/quality_gate_strict.sh diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml index 620760ae..bd964729 100644 --- a/.github/workflows/code_style.yml +++ b/.github/workflows/code_style.yml @@ -16,6 +16,15 @@ jobs: - name: Check formatting run: cargo fmt --all -- --check + deny-check: + name: cargo-deny + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Run cargo deny + uses: EmbarkStudios/cargo-deny-action@v2 + clippy: name: Clippy (${{ matrix.name }}) runs-on: ubuntu-latest @@ -71,18 +80,18 @@ jobs: # Roll-up job for branch protection code-style: - name: Code Style (fmt + clippy) + name: Code Style (fmt + clippy + deny) runs-on: ubuntu-latest if: always() - needs: [format, clippy, clippy-windows] + needs: [format, clippy, clippy-windows, deny-check] steps: - run: | - if [[ "${{ needs.format.result }}" != "success" || "${{ needs.clippy.result }}" != "success" ]]; then + if [[ "${{ needs.format.result }}" != "success" || "${{ needs.clippy.result }}" != "success" || "${{ needs.deny-check.result }}" != "success" ]]; then echo "One or more jobs failed" exit 1 fi - # clippy-windows only runs on main PRs, so skip/success are both acceptable - if [[ "${{ needs.clippy-windows.result }}" == "failure" ]]; then - echo "Windows clippy failed" + # clippy-windows only runs on main PRs, so skipped is acceptable but failure is not + if [[ "${{ needs.clippy-windows.result }}" != "success" && "${{ needs.clippy-windows.result }}" != "skipped" ]]; then + echo "Windows clippy failed: ${{ needs.clippy-windows.result }}" exit 1 fi diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..80aa2215 --- /dev/null +++ b/deny.toml @@ -0,0 +1,50 @@ +[advisories] +unmaintained = "workspace" +yanked = "deny" +ignore = [ + # Pre-existing advisories — tracked for upgrade in separate PRs + # serde_yml unsound/unmaintained — direct dep, upgrade tracked separately + "RUSTSEC-2025-0068", + # tokio-tar PAX header parsing — sandbox containers only + "RUSTSEC-2025-0111", + # wasmtime fd_renumber host panic — WASIp1, mitigated by fuel limits + "RUSTSEC-2025-0046", + # wasmtime shared linear memory unsoundness — no shared memory in our guests + "RUSTSEC-2025-0118", + # wasmtime guest-controlled resource exhaustion — mitigated by fuel/memory limits + "RUSTSEC-2026-0020", + # wasmtime wasi:http/types.fields panic — mitigated by fuel limits + "RUSTSEC-2026-0021", +] + +[licenses] +version = 2 +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-3.0", + "Unicode-DFS-2016", + "OpenSSL", + "Zlib", + "MPL-2.0", + "0BSD", + "BSL-1.0", + "CC0-1.0", + "Unlicense", + "CDLA-Permissive-2.0", +] +unused-allowed-license = "allow" + +[bans] +multiple-versions = "warn" +wildcards = "deny" + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] diff --git a/scripts/ci/quality_gate_strict.sh b/scripts/ci/quality_gate_strict.sh new file mode 100755 index 00000000..ed595964 --- /dev/null +++ b/scripts/ci/quality_gate_strict.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ensure we are running from the repository root +cd "$(git rev-parse --show-toplevel)" + +echo "==> fmt check" +cargo fmt --all -- --check + +echo "==> clippy (all warnings)" +cargo clippy --locked --all --benches --tests --examples --all-features -- -D warnings + +echo "==> cargo deny" +if ! command -v cargo-deny &>/dev/null; then + echo "ERROR: cargo-deny not installed (install with: cargo install cargo-deny)" + exit 1 +fi +cargo deny check + +echo "==> tests" +cargo test --locked