diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index da24f7e..98266fb 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -117,12 +117,37 @@ jobs: # consumer's feature unification can reach. This step is what fails the # build on a vulnerability in that pruned remainder. # - # Informational (unsound / unmaintained) advisories are reported here, - # not enforced — cargo-audit exits 0 on them. Enforcing would mean any - # newly published advisory against any transitive crate blocks every - # open PR regardless of its diff; that treadmill is a known cost in - # this org. Vulnerabilities do fail, which is the coverage deny lacks. + # Advisory posture (LAB-1152): vulnerabilities fail every run; yanked + # fails only the weekly schedule run — a yank lands against an + # unchanged lockfile and must not block unrelated PRs, and the + # schedule run exists precisely for advisories no PR diff touches. + # unsound / unmaintained warn on every event. Decision of record with + # the full per-class rationale: deny.toml [advisories]. # # Runs even when the step above failed so one run shows both verdicts. if: ${{ !cancelled() }} - run: cargo audit + run: | + set -o pipefail + cargo audit ${{ github.event_name == 'schedule' && '--deny yanked' || '' }} 2>&1 | tee "$RUNNER_TEMP/audit.log" + # The yank check fails open (cargo-audit 0.22.1 auditor.rs; verified + # locally, LAB-1152). "Updating crates.io index" prints + # unconditionally BEFORE the fetch attempt — a warm index cache + # cannot suppress it — so its presence proves only that the check + # was attempted (enabled, not disabled by e.g. a future `-n`). On + # fetch/open failure the tool warns "couldn't update|open crates.io + # index", skips the yank check entirely, and still exits 0; a + # per-package lookup failure warns "couldn't check if the package is + # yanked". The weekly run — the only one that enforces yanked — + # therefore needs both halves: the attempt line present AND every + # skip signal absent. Fail-closed on the pinned tool's wording is + # fine here: a wording change turns the weekly run red, never a PR. + if [ "${{ github.event_name }}" = "schedule" ]; then + if ! grep -q "Updating crates.io index" "$RUNNER_TEMP/audit.log"; then + echo "::error::cargo-audit never attempted the yank check (attempt line missing — check disabled?)" + exit 1 + fi + if grep -qiE "couldn't (update|open) crates.io index|couldn't check if the package is yanked" "$RUNNER_TEMP/audit.log"; then + echo "::error::cargo-audit could not evaluate yanked crates (crates.io index unavailable or lookup failed)" + exit 1 + fi + fi diff --git a/Cargo.lock b/Cargo.lock index 48b324a..1037957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,13 +106,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] @@ -217,7 +217,7 @@ version = "0.7.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -273,7 +273,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn", + "syn 2.0.117", "tempfile", "toml", ] @@ -302,9 +302,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -467,7 +467,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -485,7 +485,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -575,7 +575,7 @@ checksum = "1458c6e22d36d61507034d5afecc64f105c1d39712b7ac6ec3b352c423f715cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -634,7 +634,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1257,7 +1257,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1315,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -1729,7 +1729,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1789,7 +1789,7 @@ checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1870,6 +1870,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -1887,7 +1898,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1935,7 +1946,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1946,7 +1957,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1997,7 +2008,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2315,7 +2326,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2358,7 +2369,7 @@ checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2650,7 +2661,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2666,7 +2677,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2763,7 +2774,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -2813,7 +2824,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2834,7 +2845,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2854,7 +2865,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2875,7 +2886,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2908,7 +2919,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Makefile b/Makefile index 4290544..6fd3749 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,10 @@ test-wasm: CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner \ $(CARGO) test -p cachekit-rs --target wasm32-unknown-unknown --no-default-features --features workers,cachekitio,encryption,macros --test wasm_session_tests -# Supply-chain gate — the same commands CI runs in -# .github/workflows/security.yml, so a local pass means a CI pass. (CI runs the -# audit step even when deny fails; make stops at the first failure.) +# Supply-chain gate — deny is identical to CI's; audit runs the strict form +# (`--deny yanked`, which CI applies only on the weekly schedule run), so a +# local pass covers every CI event in .github/workflows/security.yml. (CI runs +# the audit step even when deny fails; make stops at the first failure.) # Kept out of `quick-check`: both tools fetch the RustSec advisory database over # the network, which does not belong in a per-commit loop. # Why both tools, and why --all-features: see the table in README.md. @@ -38,4 +39,4 @@ deny: $(CARGO) deny --locked --all-features check audit: - $(CARGO) audit + $(CARGO) audit --deny yanked diff --git a/README.md b/README.md index a771728..88c3b71 100644 --- a/README.md +++ b/README.md @@ -506,7 +506,11 @@ make build-wasm # wasm32-unknown-unknown (workers feature) ``` `make security` runs the same two commands as the `supply-chain` job in -`.github/workflows/security.yml`, so a local pass means a CI pass. It needs +`.github/workflows/security.yml`, with `cargo audit` in its strictest CI form +(`--deny yanked`) — so a local pass means a pass on every CI event, with one +asymmetry: the weekly run additionally proves the yank check actually executed +(see the guard in `security.yml`), so with crates.io unreachable a local run +warns and passes where the weekly run goes red. It needs `cargo-deny` and `cargo-audit` installed, and it reaches the network to refresh the RustSec advisory database — which is why it is not folded into `quick-check`. @@ -519,7 +523,8 @@ means it turns the check red — anything else is reported but not enforced: | Reads | feature-resolved dependency graph | `Cargo.lock` verbatim | | Licence allowlist, banned crates, registry/source policy | **fails** | not checked | | Vulnerabilities in crates no enabled feature activates | not seen (pruned) | **fails** | -| Unsound / unmaintained advisories on *transitive* deps | not seen — `deny.toml` narrows `unmaintained` to `workspace`; `unsound` already defaults to that scope | reports only, does **not** fail | +| Yanked crates in `Cargo.lock` | warns (feature-resolved graph only, so lockfile-only crates are missed) | warns on PR and push runs; **fails** only the weekly scheduled run (`--deny yanked`) | +| Unsound / unmaintained advisories on *transitive* deps | not seen — `deny.toml` narrows `unmaintained` to `workspace`; `unsound` already defaults to that scope | reports only, does **not** fail — deliberate (see `deny.toml`) | `--all-features` is load-bearing: the default feature set excludes the `memcached`, `redis`, `file` and `macros` backends, so a banned crate diff --git a/deny.toml b/deny.toml index 84d1816..b86a86b 100644 --- a/deny.toml +++ b/deny.toml @@ -4,11 +4,25 @@ # defaults to "all", unsound to "workspace" (direct workspace deps only). # So the line below NARROWS unmaintained from its default. It is not a # redundant restatement, and deleting it would tighten this gate. -# Consequence: transitive unsound/unmaintained advisories are not reported here -# (e.g. rand 0.8.5 / RUSTSEC-2026-0097, reached via fred and memcache). -# `cargo audit` in .github/workflows/security.yml lists them, but does not fail -# on them either — widening both scopes to "all" is open work, not covered. -# yanked: warn instead of hard-fail so renovate can fix. +# +# Warn-vs-fail posture per class (LAB-1152, decided 2026-08-31): +# - yanked: warn here, ENFORCED by `cargo audit --deny yanked` on the weekly +# schedule run in .github/workflows/security.yml. Audit is the enforcement +# point because cargo-deny grades the feature-resolved graph, which prunes +# lockfile-only crates — it scored 0 warnings while the lockfile held the +# yanked chacha20 0.10.1 (behind reqwest's unused quinn path). Warn, not +# deny, here so an upstream yank against an unchanged lockfile never blocks +# unrelated PRs. The weekly red is typically fixed same-day with +# `cargo update -p ` — a yank almost always has a compatible +# non-yanked sibling; a full-crate or semver-gapped yank needs a dependency +# bump or replacement instead (rarer, still actionable). +# - unsound / unmaintained: warn, and the scopes stay narrow — widening to +# "all" would only duplicate warnings `cargo audit` already prints every run +# from the verbatim lockfile. Not enforced anywhere: neither class +# guarantees a fixed version exists (today's rand 0.8.5 / RUSTSEC-2026-0097, +# via fred 9.4 + memcache 0.19, has no compatible upgrade), so failing on +# them forces an ignore-list that accumulates — a worse posture than an +# honest warning. yanked = "warn" unmaintained = "workspace"