Replace cargo audit with cargo deny for CLI dependency policy#96
Replace cargo audit with cargo deny for CLI dependency policy#96juangaitanv wants to merge 1 commit into
Conversation
cargo audit only checked RustSec advisories. For a binary we distribute to customers, that left licenses, sources, and bans untested. cargo deny covers all three plus advisories from the same RustSec DB — a strict superset — so consolidate to one supply-chain tool. - deny.toml: advisories (deny), licenses (permissive allowlist + option-ext MPL-2.0), sources (lock to crates.io), bans (warn on duplicates/wildcards). - harness: cmd_audit/_cmd_audit_inner -> cmd_deny/_cmd_deny_inner; ci + dispatch + help/header updated. Local stays lenient, CI stays strict. - test.yml: install cargo-deny instead of cargo-audit. - Cargo.toml: mark corgea publish=false. It ships via maturin (npm + pip), never crates.io, and its own LICENSE is LGPL-2.1; publish=false makes it "private" so deny skips license checks on our own source. - AGENTS.md: document ./harness deny. Verified: cargo deny check passes (advisories/licenses/sources/bans), negative check fails when MPL-2.0 is dropped, ./harness ci green.
|
|
||
| [advisories] | ||
| version = 2 | ||
| # RustSec DB: fail on vulnerable/unmaintained/unsound/yanked crates. |
There was a problem hiding this comment.
Evidence: this comment says the advisory gate fails on yanked crates, but the new [advisories] config only sets version = 2 and does not set yanked. cargo-deny's current default for yanked crates is warn, so cargo deny check will not fail CI when a yanked crate is present. Impact: the replacement policy is weaker than the stated production gate and can ship a yanked dependency while reporting only a warning. Concrete fix: add yanked = "deny" under [advisories] (and keep ignores explicit/reasoned if a yanked crate must be tolerated), or change the stated policy if yanked crates are intentionally non-blocking.
| "MIT-0", | ||
| "Zlib", | ||
| "BSL-1.0", | ||
| "MPL-2.0", |
There was a problem hiding this comment.
Evidence: lines 11-12 say MPL is being allowed only for option-ext, but putting "MPL-2.0" in the global licenses.allow list permits any current or future dependency with MPL-2.0. Impact: a future dependency/license drift to MPL-2.0 will pass the production license gate without reviewer visibility, which weakens the validation this PR is adding. Concrete fix: remove "MPL-2.0" from the global allowlist and add a crate-scoped exception instead, e.g. exceptions = [{ crate = "option-ext", allow = ["MPL-2.0"] }] under [licenses] (or the equivalent [[licenses.exceptions]] form supported by the chosen cargo-deny version).
| if cargo audit --version >/dev/null 2>&1; then | ||
| run "Dep audit" 0 -- cargo audit | ||
| if cargo deny --version >/dev/null 2>&1; then | ||
| run "Dep policy" 0 -- cargo deny check |
There was a problem hiding this comment.
Evidence: the harness runs cargo deny check without --locked. Unlike the old cargo audit path, which audited the committed Cargo.lock directly, cargo-deny builds its graph through Cargo metadata and supports a global --locked flag specifically to assert the lockfile will not change. Impact: if Cargo.toml is changed without committing the lockfile update, the dep-policy gate can resolve/scan a transient graph in CI instead of failing fast on the stale committed lockfile, so the reviewed lockfile is not necessarily what the policy validated. Concrete fix: run cargo deny --locked check from the harness (and keep CI using that path) so dependency-policy validation is tied to the committed lockfile.
Why
cargo auditonly checked RustSec advisories. For a binary we ship to customers via npm + pip, that left three supply-chain dimensions untested:cargo denycovers all three plus advisories from the same RustSec DB — a strict superset ofcargo audit. One supply-chain tool, no redundant RustSec fetch.Changes
deny.toml(new) —version = 2schema. advisories: deny vuln/unmaintained/unsound/yanked. licenses: permissive allowlist +option-ext's file-level MPL-2.0 (transitive viadirs). bans: warn on duplicate versions / wildcards. sources: lock to crates.io (unknown-registry/unknown-git= deny).harness—cmd_audit/_cmd_audit_inner→cmd_deny/_cmd_deny_inner;cmd_ci, dispatch, header, and help updated. Local stays lenient (skips with install hint if cargo-deny absent), CI stays strict..github/workflows/test.yml— installcargo-denyinstead ofcargo-audit.Cargo.toml— markcorgeapublish = false.AGENTS.md— document./harness deny.Note: plan assumption corrected
The plan assumed the root
corgeacrate had no license and thatprivate = { ignore = true }alone would skip it. In reality:LICENSEfile is GNU LGPL-2.1, which cargo-deny reads as the crate's license (LGPL-2.1-or-later) — so it was rejected, not "no license".private.ignoreonly skips crates markedpublish = false, whichcorgeawas not.Fix: added
publish = falsetoCargo.toml. It's factually correct —corgeaships via maturin (npm + pip), nevercargo published to crates.io — and it makesprivate.ignorefire so our own LGPL source is exempt from the dependency-license rules. (This does not weaken the policy for actual dependencies.)Verification
cargo deny check→ all four checks pass (advisories / licenses / sources / bans; duplicates are warn-only)../harness deny→ green ✓../harness ci→ green ✓ (clippy strict, fmt, deny, tests + coverage, 35 passed).MPL-2.0from the allowlist →option-ext v0.2.0rejected,licenses FAILED(exit 4). The policy bites.