diff --git a/.cargo/yanked-register.txt b/.cargo/yanked-register.txt new file mode 100644 index 00000000..68a19471 --- /dev/null +++ b/.cargo/yanked-register.txt @@ -0,0 +1,42 @@ +# Yanked crates the security audit tolerates, and for how long. +# +# `cargo audit --deny yanked` fails on any yanked crate and has no way to name +# one it should leave alone: its `--ignore` takes advisory IDs, and a yanked +# crate has none. `scripts/check-yanked.sh` is the audit gate instead. It keeps +# `--deny yanked` and `--deny unmaintained`, fails on every vulnerability, and +# lets a yanked crate through only when this file names that exact version. +# +# An entry is a loan, not a decision: +# - it names one crate at one version, so a second yanked crate, or the same +# one at another version, fails the audit; +# - `expires` has to be within 60 days of `recorded`, and an expired entry +# fails the audit, which forces somebody to look again; +# - an entry that matches nothing fails the audit, so the line is removed the +# day the crate leaves the tree rather than left to rot. +# +# Entries are blocks of `key: value`, a block starting at `crate:`. A value may +# wrap onto lines that start with whitespace. Every key below is required. + +crate: zip +version: 8.6.0 +path: dpp-integrator -> calamine -> zip +rationale: Yanked upstream, and it cannot be replaced from here. Every zip from + 8.0.0 through 9.0.2 is yanked and only 9.0.3 and later are live, but + calamine 0.36.1, its latest release, requires zip ^8.6, so calamine + pulls 8.6.0 into the + tree whatever this workspace asks for. A direct bump to 9.0.x would + leave 8.6.0 in place for calamine and put two zip versions in the + build, which dpp-integrator avoids on purpose: its direct zip, + used to pre-scan XLSX for zip and dimension bombs, is + version-matched to calamine's. Git sources are denied (deny.toml), + so patching in a calamine fork is not an option either. Yanking is + the maintainer's "do not use", and this is a bounded exception to + it, not agreement with it. +owner: LKSNDRTMLKV +recorded: 2026-10-16 +expires: 2026-12-15 +release-on: calamine releases a version that requires zip 9 (tafia/calamine#741). + Then bump calamine and the direct zip together in one change (zip + 9.0.3 or later), adjust the `entry.name()` calls in xlsx_parser.rs + to whatever zip 9 returns, re-run the XLSX bomb tests, and delete + this entry. diff --git a/.github/workflows/advisories.yml b/.github/workflows/advisories.yml index 1d644851..2286c895 100644 --- a/.github/workflows/advisories.yml +++ b/.github/workflows/advisories.yml @@ -48,19 +48,24 @@ jobs: toolchain: stable # The same invocation and the same flags as the pull-request path, so a - # nightly failure and a CI failure mean the same thing. `--deny yanked` + # nightly failure and a CI failure mean the same thing. The yanked check # catches the other thing that changes without a commit: a crate - # withdrawn from the registry after we pinned it. + # withdrawn from the registry after we pinned it. A yanked crate the + # register tolerates carries an expiry date, which is another thing that + # passes on a calendar and not on a commit, so this run is where it is + # noticed. - name: Install cargo-audit run: cargo install cargo-audit --locked - - name: cargo audit - run: cargo audit --deny yanked --deny unmaintained + - name: cargo audit (a yanked crate needs a register entry) + run: bash scripts/check-yanked.sh # Reason 2 above. This is the check that cannot be reached by pushing. - name: Suppression register is current run: bash scripts/check-audit-register.sh - name: Suppression register checker self-test run: bash scripts/check-audit-register.test.sh + - name: Yanked-crate checker self-test + run: bash scripts/check-yanked.test.sh # Bans, licences and sources are properties of the tree and cannot change # without a commit, so they stay on the pull-request run. Advisories stay diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a907736..bb17969b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -528,15 +528,18 @@ jobs: workspaces: dpp-engine - name: Install cargo-audit run: cargo install cargo-audit --locked - - name: cargo audit + - name: cargo audit (a yanked crate needs a register entry) working-directory: dpp-engine - run: cargo audit --deny yanked --deny unmaintained + run: bash scripts/check-yanked.sh - name: Suppression register is current working-directory: dpp-engine run: bash scripts/check-audit-register.sh - name: Suppression register checker self-test working-directory: dpp-engine run: bash scripts/check-audit-register.test.sh + - name: Yanked-crate checker self-test + working-directory: dpp-engine + run: bash scripts/check-yanked.test.sh - name: Install cargo-deny uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2.87.27 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ce1393..83ce497b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -643,6 +643,38 @@ under the pre-1.0 conventions in [VERSIONING.md](docs/governance/VERSIONING.md): registry-contract change could not pass it otherwise. It is its own commit, and the only lockfile change in that branch. +- **`wasmtime` and `wasmtime-wasi` 49.0.2 → 49.0.3**, clearing RUSTSEC-2026-0352 + to RUSTSEC-2026-0364, which had turned the security audit red on `main`. The + one that matters most is in `wasmtime-wasi` (-0352): renaming a symlink whose + name ends in a slash lets a guest step out of a directory it was given. It is + listed for macOS and FreeBSD hosts only, and this host preopens no directory + (see `runtime::build_store`), so a plugin here has no directory to step out + of. The other twelve are in `wasmtime` itself and are mostly host panics in the + component-model and async paths (-0353 to -0358, -0360, -0361), plus a stack + overflow dropping a long chain of recursive types (-0359), an async-entrypoint + requirement that is not enforced (-0362), component-model handle tables + capped per instance and charged to no limit (-0363), and exception tags that + can leave host data structures unaligned (-0364). The host runs plugins + synchronously as `wasm32-wasip1` modules, which keeps it away from most of + those paths, but which of them a plugin can reach has not been established + for each, and the sandbox is what they are about, so the bump is not left + waiting on that. The existing `wasmtime = "49"` requirement already permitted + the patch, so no manifest changed. Nothing else in the lockfile moved except + the `cranelift`, `pulley` and `wiggle` crates that release pins to. + + `zip` 8.6.0 is yanked upstream, and `cargo audit --deny yanked` has no way to + leave one crate alone (`--ignore` takes advisory IDs, which a yanked crate does + not have), so it kept the audit red, and with it every pull request, this one + included. It cannot be replaced from here: calamine, whose latest release + requires `zip ^8.6`, pulls it in, and every zip from 8.0.0 through 9.0.2 is + yanked. The audit is now `scripts/check-yanked.sh`. It still denies yanked and + unmaintained crates and fails on every vulnerability, but lets a yanked crate + through when `.cargo/yanked-register.txt` names that exact version with an + owner, a reason and an expiry that is at most 60 days out. An entry that has + expired, or that no longer matches a yanked crate, fails the audit, so the + exception ends on a date or when calamine moves to zip 9, whichever comes + first. The register holds `zip 8.6.0` and expires on 15 December. + ## [0.14.0] - 2026-09-24 ### Breaking diff --git a/Cargo.lock b/Cargo.lock index 6bd81b6d..ed3a02ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -183,7 +183,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1219,7 +1219,7 @@ dependencies = [ "maybe-owned", "rustix", "rustix-linux-procfs", - "windows-sys 0.61.2", + "windows-sys 0.60.2", "winx", ] @@ -1548,27 +1548,27 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658e8623bea025d142fb11c299b8c6572ffe9fd663b52887c4dc124735cc130b" +checksum = "f6d504c8c7ba3f3accaf8e98b7096beb98e1a271404357744c7e9e730c6865ff" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b12b8c55c31c3d8598c2de9a657c3b978f905f6886c43e02216e0b15fe45a3" +checksum = "c065757905596bc06e82969ebd7b92d691b7fd710a7e9e8181ae0c30dab588d0" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894b984f82c5a36f05fdd4973fbf4d37d69cd5cdf8c16e8ffe3af4a9cdfe193c" +checksum = "5ca7162cd4d4fc5f9f2f88d1459d939882203d93a8c5a19ef2c405b2e82ef13f" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -1576,9 +1576,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1e4c34abe94193ada09f821f474be87ac8a1fbbacd87058571ef04c1bf4009" +checksum = "0ec2ba2d4f3d959ec4023a10266c3dcfb28381d1938d507a9a5e1abb78bbcc88" dependencies = [ "serde", "serde_derive", @@ -1587,9 +1587,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63826cef0bf70e61d56cc930aa4ee423638f66dc3cfc10d683e73138a6d177b8" +checksum = "51a64cb821d5fed56bdd6053acd36fa928cd82b70bf6b19b8b14b8b27d047b9a" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -1618,9 +1618,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26de7849112293681a19f7525c689e1d73dc6eb2687355170fb7458c4da8d353" +checksum = "142be1e267a7497ba3c2b5a829eaf5387b92b9bcdba6e0a6020f1ffccae7f2b8" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -1631,24 +1631,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df315a8b1cfd6e620971e3d4249bd2c4528167717e8f48a6317a87c55891bcb8" +checksum = "0e41f75410da6cce8601330137d73718f679db81cb790a33de3871b5c90a0de3" [[package]] name = "cranelift-control" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3fff7610fb17f228f16598e1a044db79a12806ece7496ff2c69cf1a27a79a5" +checksum = "0c2616c6d01c14921ac9289ef65eb7702d9c760dfdc4537a8ee5bf38ea2832c6" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6158b1ac381792ad11e670abbcd15db8b1737e84bfa8a7f2bd7eb41388a92530" +checksum = "a03e8b44c4877cf2fb21e5788d514af6fd291a066ed9ae37adf114a25fecb4aa" dependencies = [ "cranelift-bitset", "serde", @@ -1658,9 +1658,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099361a0919067086dcde624a281ce719834cebf8544d6c24646f5e924107234" +checksum = "366ff7cb014bd08752210eba55a650969f8db9b52670d69d2ea0b23e1e9e3edf" dependencies = [ "cranelift-codegen", "hashbrown 0.17.1", @@ -1671,15 +1671,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2f30ea3fd5a11f7d3473976d3b62b5b3f2b62fa943c85e0331d88bf505082c" +checksum = "9ea6526ebf9064fff8ce3b1e26afad7027f9e6bb0e45d2b9200e4cbab8343345" [[package]] name = "cranelift-native" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397508a8d817dc74019603ac5a4c4e40b4df627e5aed4f2f8088345d4c01d401" +checksum = "b44d323f3e5108a8a5bc61f3acd227285ece066e19afe9aece211d66b34a64ea" dependencies = [ "cranelift-codegen", "libc", @@ -1688,9 +1688,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.136.2" +version = "0.136.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4944ef05513e1c1a85a945cf8f29d95ddea8bcbb19e0bfd4e71063450967420" +checksum = "fc173168f291ff3b95b5d0769d6a05b1eaef5e6d74c2600affab8396b4e7b420" [[package]] name = "crc" @@ -2191,7 +2191,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3061,7 +3061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -4043,7 +4043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" dependencies = [ "io-lifetimes 3.0.1", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -4581,7 +4581,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5364,9 +5364,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b66b7448cb92d709f2924de98b5ba5b5ce4ea08e6d6b9c3b403fcd88d122ca2" +checksum = "def59f28ccaf2cb4f8dfaac83197fd7d56a4470a70e4f2589454c731370101c5" dependencies = [ "cranelift-bitset", "log", @@ -5376,9 +5376,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185b9b4cd91f85110f48f8c0314757dfb2bb48c40f5d91055ea666cbbcbf317b" +checksum = "3a1834d3b3ef994f266a0c9ed29a8214f2c2faaa9b1c44babfa57566bb05af83" dependencies = [ "proc-macro2", "quote", @@ -5494,7 +5494,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -5993,7 +5993,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -6062,7 +6062,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -6657,7 +6657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7010,10 +7010,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -7572,7 +7572,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7944,9 +7944,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1801b9c9f91e16e352ed77656824e7c4556a7538dbfc1a50b37243f0e12ea045" +checksum = "4fbdd39f5c66f63ba6b5b31391b324b7ade7237d29c866cdb561330118ef436e" dependencies = [ "addr2line", "async-trait", @@ -7986,9 +7986,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f397969b8daf208e45120036be74956e99364ce916d5b5d67f9e9f0fe40e5477" +checksum = "e915c27826de4f7b4411b61fd6db32ca448657a5136b8c8e8874a15e90cecbf4" dependencies = [ "anyhow", "cpp_demangle", @@ -8017,9 +8017,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47c428b868b59d51a9f77e24bff02cc321f8ec0faeba5c40f7e4c1cddd43a64" +checksum = "d95f531a0b809fd9fd158be6133c97bd2fc628ac47f5812aff3e3b5624eb02c2" dependencies = [ "anyhow", "proc-macro2", @@ -8032,15 +8032,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fe14bd5ea6444a09f6baa3865559c93c91158930e7fe6d127429a9372f6165" +checksum = "e754a3ff5690c34eb4713bd7c65f60dd607ead4e39042cbf52ac8011de14455b" [[package]] name = "wasmtime-internal-core" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2053fd1d90080e502ca4ea39d86c3528587a40220b9572a874c8b742c219a00c" +checksum = "ba00c5db77adb0749ee059068e5a417bfac3d8969f5a3479d9bd1ca8c1ba248d" dependencies = [ "hashbrown 0.17.1", "libm", @@ -8049,9 +8049,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e96d6c55401e68e40e3f74078ec96f92fe9b8a7e90e885fb87265e69eea559" +checksum = "97ceeb4325712a253f463e50a69b5a547fbbecd52e45612360c543eaa25a03ff" dependencies = [ "cranelift-codegen", "cranelift-control", @@ -8075,9 +8075,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b913875bab1b78fada489e55d5e0a10b53286fb4bb09f5657212f549c9af36c" +checksum = "3eb3f21bc32b5382ea95f83ce9a5f2f62b68f1b25a37d0802a41a65236eae5ae" dependencies = [ "cc", "libc", @@ -8089,9 +8089,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0356a1a09dee1239904e09e962456d9f6edfa5da61c5e3e1b0e4669710f48fba" +checksum = "e786f9925126e0d2e466c5308e572e8b6226f4047596228a3d63d99528a78949" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -8099,9 +8099,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dead9a2d7b5d835698a2e1a32006e7d0b5711f4697b24bab1564bb2488e3d7a" +checksum = "92cb1dd5da78f5b21d65e91063bdd8c2eb343c1f077a1dcbc29003d4a794abb0" dependencies = [ "libc", "wasmtime-internal-core", @@ -8110,9 +8110,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ffac8fc893de2958b263d9d7274cf4c531ba9ff582558348d4e28d97b8a0f2" +checksum = "d0243d244f6dd07ccd114e6bb099e8dd1197b47de5c054576adc9483f80f65d8" dependencies = [ "cranelift-codegen", "log", @@ -8122,9 +8122,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a20f0dcb6c6a006363d3f85b5c633675730487b0dc23726b2d40495872407fb" +checksum = "20fa5616cd875ec78f5b92020fa47ced7c8bb923079185c8475751098b8c4a69" dependencies = [ "proc-macro2", "quote", @@ -8133,9 +8133,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80758f73f5f5daacd8a6d1a8513336a4f9a58d26c4f1c73eb80502cb143e2626" +checksum = "cad5e8cef56790d4741b62f7b6c971af53b2ad4c2d1603f8ffe86872763147aa" dependencies = [ "anyhow", "bitflags", @@ -8147,9 +8147,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac947c8478d8ea84ef4120d04f984012a514e493fe16fa5bb72906d7e55addb" +checksum = "284555904d42017e049fd57c05810b7975075c322481c045f99b07385fb7a5ba" dependencies = [ "async-trait", "bitflags", @@ -8172,9 +8172,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65d70efbe70631acbb4c6cf46eea543c75908f01e2fd09550e5a89631693304" +checksum = "ebaf573aae85636595bdb976b6c76e9277e1f81db067aff6764cb256e0c17dff" dependencies = [ "async-trait", "bytes", @@ -8315,9 +8315,9 @@ checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" [[package]] name = "wiggle" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7c3a2e8ec4789bb82e4bf04b0dba44b02ce05beba6cbc6eb992b8f0b93c9cd" +checksum = "ad3dcdc2980b91d0e7db6975caee4f553c6e37fd8e1464a38b09baceed3fe568" dependencies = [ "bitflags", "thiserror 2.0.21", @@ -8329,9 +8329,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761bf518bf5972dd0ddb8c3c693b6a52ed826e25d10bfae89b1368327c86030d" +checksum = "4765433ad668e225812b956e0f598011e5cdab4d1a57b2d295ab6863e36ff231" dependencies = [ "heck", "proc-macro2", @@ -8343,9 +8343,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "49.0.2" +version = "49.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bdaa435787bc4bb662020aea37538745fc60b4629d7f37864a09195cec0e1eb" +checksum = "d94c6623099d11a3fee39986492e803dd0db6676c9d40ab9553a7edd29f317e5" dependencies = [ "proc-macro2", "quote", @@ -8375,7 +8375,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/justfile b/justfile index 9b31aef1..70029270 100644 --- a/justfile +++ b/justfile @@ -341,14 +341,19 @@ mod-rs-check: spec-version-check: bash scripts/spec-version-check.sh -# Run security audit against the RustSec advisory database. --deny yanked/ -# unmaintained so those stop passing silently (a yanked crate is a -# maintainer's explicit "don't use this") — kept separate from vulnerability -# denial (already the default) so the two classes can be tuned independently. +# Run security audit against the RustSec advisory database. Yanked and +# unmaintained crates are denied so they stop passing silently (a yanked crate +# is a maintainer's explicit "don't use this") — kept separate from +# vulnerability denial (already the default) so the classes can be tuned +# independently. `check-yanked.sh` runs `cargo audit --deny yanked --deny +# unmaintained` and lets a yanked crate through only when +# `.cargo/yanked-register.txt` names that exact version, with an expiry: +# `cargo audit` itself cannot leave one yanked crate alone. audit: - cargo audit --deny yanked --deny unmaintained + bash scripts/check-yanked.sh bash scripts/check-audit-register.sh bash scripts/check-audit-register.test.sh + bash scripts/check-yanked.test.sh cargo deny check bans licenses sources # Build documentation (engine does not gate docs with -D warnings yet) diff --git a/scripts/check-yanked.sh b/scripts/check-yanked.sh new file mode 100755 index 00000000..f29c4977 --- /dev/null +++ b/scripts/check-yanked.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +# The audit gate for what `cargo audit` denies. +# +# A vulnerability and an unmaintained crate always fail it. A yanked crate fails +# it too, unless `.cargo/yanked-register.txt` names that exact crate and version +# with an owner, a reason and an expiry that has not passed. +# +# Why it exists. `cargo audit --deny yanked` has no way to leave one crate +# alone: `--ignore` takes advisory IDs and a yanked crate has none. So when a +# crate we cannot replace is yanked, the choice was between a red audit on every +# pull request, which blocks every merge including a security fix, and dropping +# `--deny yanked` for everything. This is the third option: keep the flag, and +# make each exception a named, dated, expiring line. +# +# Checks (any failure is fatal): +# 1. every yanked crate cargo audit reports is in the register at that version +# 2. every register entry has all of its fields, a well-formed date range, an +# `expires` that has not passed, and an `expires` within 60 days of +# `recorded` +# 3. no register entry is stale: each must still match a reported yanked crate +# 4. every vulnerability and unmaintained crate is accounted for as a failure, +# and cargo audit's own count of what it denied matches what was read, so a +# change in its output format cannot make a finding disappear +# +# Usage: scripts/check-yanked.sh [register] [saved-audit-output] +# With a saved output the script reads it instead of running cargo audit, +# which is how check-yanked.test.sh drives it. YANKED_TODAY=YYYY-MM-DD pins the +# date for the same reason. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +REGISTER="${1:-.cargo/yanked-register.txt}" +OUTPUT_FILE="${2:-}" +TODAY="${YANKED_TODAY:-$(date +%F)}" +MAX_DAYS=60 +STATUS=0 +fail() { echo "FAIL: $*" >&2; STATUS=1; } + +# ── What cargo audit says ─────────────────────────────────────────────────── +audit_rc=0 +if [ -n "$OUTPUT_FILE" ]; then + out="$(cat "$OUTPUT_FILE")" + # A saved run has no exit status; cargo audit prints an `error:` line exactly + # when it exits non-zero. + if grep -q '^error:' <<< "$out"; then audit_rc=1; fi +else + out="$(cargo audit --deny yanked --deny unmaintained --color never 2>&1)" || audit_rc=$? +fi +printf '%s\n' "$out" +echo + +# One line per finding: crate|version|kind|advisory id. `kind` is what the +# `Warning:` line says, and `vulnerability` when there is none. +findings="$(awk ' + function flush() { + if (crate != "") print crate "|" ver "|" (kind == "" ? "vulnerability" : kind) "|" id + crate = ""; ver = ""; kind = ""; id = "" + } + { gsub(/[ \t\r]+$/, "") } + /^Crate:/ { flush(); sub(/^Crate:[ \t]*/, ""); crate = $0; next } + /^Version:/ { sub(/^Version:[ \t]*/, ""); ver = $0; next } + /^Warning:/ { sub(/^Warning:[ \t]*/, ""); kind = $0; next } + /^ID:/ { sub(/^ID:[ \t]*/, ""); id = $0; next } + END { flush() } +' <<< "$out")" + +yanked="$(awk -F'|' '$3 == "yanked" { print $1 " " $2 }' <<< "$findings")" +seen_denied_warnings="$(awk -F'|' '$3 == "yanked" || $3 == "unmaintained"' <<< "$findings" | grep -c . || true)" +seen_vulnerabilities="$(awk -F'|' '$3 == "vulnerability"' <<< "$findings" | grep -c . || true)" + +said_denied_warnings="$(grep -oE '^error: [0-9]+ denied warning' <<< "$out" | grep -oE '[0-9]+' || true)" +said_vulnerabilities="$(grep -oE '^error: [0-9]+ vulnerabilit' <<< "$out" | grep -oE '[0-9]+' || true)" +said_denied_warnings="${said_denied_warnings:-0}" +said_vulnerabilities="${said_vulnerabilities:-0}" + +if [ "$audit_rc" -ne 0 ]; then + if [ "$said_denied_warnings" -eq 0 ] && [ "$said_vulnerabilities" -eq 0 ]; then + fail "cargo audit failed without reporting a finding this script can read (see its output above)" + fi + if [ "$seen_denied_warnings" -ne "$said_denied_warnings" ]; then + fail "cargo audit denied $said_denied_warnings warning(s) and this script read $seen_denied_warnings" + fi + if [ "$seen_vulnerabilities" -ne "$said_vulnerabilities" ]; then + fail "cargo audit found $said_vulnerabilities vulnerabilit(ies) and this script read $seen_vulnerabilities" + fi +fi + +# Vulnerabilities and unmaintained crates are never tolerated here. A +# vulnerability that cannot be fixed is an `ignore` entry in .cargo/audit.toml, +# with the reachability argument that file demands. +while IFS='|' read -r crate ver kind id; do + [ -z "$crate" ] && continue + case "$kind" in + vulnerability) fail "$crate $ver has ${id:-an advisory}; fix it, or record it in .cargo/audit.toml" ;; + unmaintained) fail "$crate $ver is unmaintained (${id:-no id}); replace it, or record it in .cargo/audit.toml" ;; + esac +done <<< "$findings" + +# ── The register ──────────────────────────────────────────────────────────── +# One line per entry: crate|version|recorded|expires|missing fields. +records="$(awk ' + function flush( i, n, miss, req) { + if (!started) return + n = split("version path rationale owner recorded expires release-on", req, " ") + miss = "" + for (i = 1; i <= n; i++) if (f[req[i]] == "") miss = miss " " req[i] + print crate "|" f["version"] "|" f["recorded"] "|" f["expires"] "|" miss + delete f; crate = ""; started = 0 + } + { gsub(/\r$/, "") } + /^[ \t]*#/ || /^[ \t]*$/ { next } + /^crate:/ { flush(); sub(/^crate:[ \t]*/, ""); gsub(/[ \t]+$/, ""); crate = $0; started = 1; cur = "crate"; next } + /^[a-z-]+:/ { + key = $0; sub(/:.*/, "", key) + val = $0; sub(/^[a-z-]+:[ \t]*/, "", val); gsub(/[ \t]+$/, "", val) + f[key] = val; cur = key; next + } + /^[ \t]+[^ \t]/ { + v = $0; gsub(/^[ \t]+/, "", v); gsub(/[ \t]+$/, "", v) + if (started && cur != "crate") f[cur] = f[cur] " " v + next + } + END { flush() } +' "$REGISTER")" + +date_ok() { [[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && date -d "$1" +%F >/dev/null 2>&1; } + +registered="" +while IFS='|' read -r crate ver recorded expires missing; do + [ -z "$crate" ] && continue + label="register entry '$crate $ver'" + if [ -n "$missing" ]; then + fail "$label lacks:$missing" + continue + fi + if grep -qxF "$crate $ver" <<< "$registered"; then + fail "$label appears twice" + fi + registered+="$crate $ver"$'\n' + if ! date_ok "$recorded" || ! date_ok "$expires"; then + fail "$label needs recorded and expires as real YYYY-MM-DD dates (got '$recorded' and '$expires')" + continue + fi + if [[ "$expires" < "$recorded" ]]; then + fail "$label expires ($expires) before it was recorded ($recorded)" + fi + limit="$(date -d "$recorded + $MAX_DAYS days" +%F)" + if [[ "$expires" > "$limit" ]]; then + fail "$label runs past $limit, $MAX_DAYS days after it was recorded; a loan this long is a decision, and belongs somewhere else" + fi + if [[ "$expires" < "$TODAY" ]]; then + fail "$label expired on $expires; confirm the crate is still stuck, or remove it" + fi +done <<< "$records" + +# ── Each side against the other ───────────────────────────────────────────── +while read -r crate ver; do + [ -z "$crate" ] && continue + if ! grep -qxF "$crate $ver" <<< "$registered"; then + fail "$crate $ver is yanked and is not in $REGISTER; replace it, or add an entry that says why it cannot be and when it expires" + fi +done <<< "$yanked" + +while read -r crate ver; do + [ -z "$crate" ] && continue + if ! grep -qxF "$crate $ver" <<< "$yanked"; then + fail "register entry '$crate $ver' matches no yanked crate cargo audit reported; remove it" + fi +done <<< "$registered" + +if [ "$STATUS" -eq 0 ]; then + n="$(grep -c . <<< "$registered" || true)" + echo "ok - the audit is clean apart from ${n:-0} registered yanked crate(s), none expired" +fi +exit "$STATUS" diff --git a/scripts/check-yanked.test.sh b/scripts/check-yanked.test.sh new file mode 100755 index 00000000..9dd6c0bd --- /dev/null +++ b/scripts/check-yanked.test.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# Merge gate for check-yanked.sh itself: proves the checker goes red in each +# direction it exists to, and green only for the one case it tolerates. It runs +# the checker against saved `cargo audit` output, never against the network, with +# the date pinned. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +SCRATCH="$(mktemp -d)" +trap 'rm -rf "$SCRATCH"' EXIT + +export YANKED_TODAY=2026-10-20 + +PASS=0 +FAIL=0 + +# ── cargo audit output, as it prints it (--color never) ───────────────────── +HEAD=' Fetching advisory database from `https://github.com/RustSec/advisory-db.git` + Loaded 1329 security advisories (from /home/runner/.cargo/advisory-db) + Updating crates.io index + Scanning Cargo.lock for vulnerabilities (814 crate dependencies)' + +YANKED_ZIP='Crate: zip +Version: 8.6.0 +Warning: yanked +Dependency tree: +zip 8.6.0 +├── dpp-integrator 0.14.0 +│ └── dpp-node 0.14.0 +└── calamine 0.36.1 + └── dpp-integrator 0.14.0 +' + +VULNERABLE='Crate: wasmtime +Version: 49.0.2 +Title: Panic when invoking an async-lifted function via `call` +Date: 2026-10-14 +ID: RUSTSEC-2026-0353 +URL: https://rustsec.org/advisories/RUSTSEC-2026-0353 +Severity: 5.9 (medium) +Solution: Upgrade to >=48.0.6, <49.0.0 OR >=49.0.3 +Dependency tree: +wasmtime 49.0.2 +└── dpp-plugin-host 0.14.0 +' + +UNMAINTAINED='Crate: paste +Version: 1.0.15 +Warning: unmaintained +Title: paste - no longer maintained +Date: 2024-10-07 +ID: RUSTSEC-2024-0436 +URL: https://rustsec.org/advisories/RUSTSEC-2024-0436 +Dependency tree: +paste 1.0.15 +└── dpp-node 0.14.0 +' + +UNSOUND='Crate: some-crate +Version: 0.3.1 +Warning: unsound +Title: Unsound read +Date: 2026-01-01 +ID: RUSTSEC-2026-0001 +URL: https://rustsec.org/advisories/RUSTSEC-2026-0001 +Dependency tree: +some-crate 0.3.1 +└── dpp-node 0.14.0 +' + +# ── registers ─────────────────────────────────────────────────────────────── +# One valid entry; the variants below are derived from it by `sed`, and each +# derivation is asserted, so a fixture that stopped differing cannot pass quietly. +VALID_ENTRY='crate: zip +version: 8.6.0 +path: dpp-integrator -> calamine -> zip +rationale: Yanked upstream and held in by calamine, which + requires zip ^8.6. +owner: LKSNDRTMLKV +recorded: 2026-10-16 +expires: 2026-12-15 +release-on: calamine releases a version that requires zip 9.' + +EMPTY_REGISTER='# nothing is tolerated' + +write() { printf '%s\n' "$2" > "$SCRATCH/$1"; } + +write clean.out "$HEAD" +write zip.out "$HEAD +$YANKED_ZIP +error: 1 denied warning found!" +write vuln.out "$HEAD +$VULNERABLE +error: 1 vulnerability found!" +write vuln-and-zip.out "$HEAD +$VULNERABLE +$YANKED_ZIP +error: 1 denied warning found! +error: 1 vulnerability found!" +write unmaintained.out "$HEAD +$UNMAINTAINED +error: 1 denied warning found!" +write unsound.out "$HEAD +$UNSOUND +warning: 1 allowed warning found" +write two-zips.out "$HEAD +$YANKED_ZIP +$(sed 's/8\.6\.0/8\.6\.1/' <<< "$YANKED_ZIP") +error: 2 denied warnings found!" +write zip-other-version.out "$HEAD +$(sed 's/8\.6\.0/8\.6\.1/' <<< "$YANKED_ZIP") +error: 1 denied warning found!" +write unreadable.out "$HEAD +error: could not fetch the advisory database" +write miscounted.out "$HEAD +$YANKED_ZIP +error: 3 denied warnings found!" + +write valid.reg "# header +$VALID_ENTRY" +write empty.reg "$EMPTY_REGISTER" +write expired.reg "$(sed 's/^expires:.*/expires: 2026-10-19/' <<< "$VALID_ENTRY")" +write too-long.reg "$(sed 's/^expires:.*/expires: 2027-06-01/' <<< "$VALID_ENTRY")" +write backwards.reg "$(sed 's/^expires:.*/expires: 2026-10-01/' <<< "$VALID_ENTRY")" +write bad-date.reg "$(sed 's/^expires:.*/expires: soon/' <<< "$VALID_ENTRY")" +write no-owner.reg "$(grep -v '^owner:' <<< "$VALID_ENTRY")" +write no-release.reg "$(grep -v '^release-on:' <<< "$VALID_ENTRY")" +write twice.reg "$VALID_ENTRY + +$VALID_ENTRY" +write other-version.reg "$(sed 's/^version:.*/version: 8.6.1/' <<< "$VALID_ENTRY")" + +# Each derived fixture must actually differ from the valid one. +for f in expired too-long backwards bad-date no-owner no-release other-version; do + if cmp -s "$SCRATCH/$f.reg" <(printf '%s\n' "$VALID_ENTRY"); then + echo "FAIL - fixture $f.reg did not differ from the valid entry" + exit 1 + fi +done + +# ── assertions ────────────────────────────────────────────────────────────── +expect_pass() { + local name="$1" reg="$2" out="$3" + if bash scripts/check-yanked.sh "$SCRATCH/$reg" "$SCRATCH/$out" >/dev/null 2>&1; then + echo "ok - $name" + PASS=$((PASS + 1)) + else + echo "FAIL - $name (expected the checker to pass, it did not)" + FAIL=$((FAIL + 1)) + fi +} + +# Fails for the stated reason, not merely non-zero: `fail()` accumulates, so a +# fixture can trip several checks at once. +expect_fail_with() { + local name="$1" reg="$2" out="$3" needle="$4" got + if got="$(bash scripts/check-yanked.sh "$SCRATCH/$reg" "$SCRATCH/$out" 2>&1)"; then + echo "FAIL - $name (expected the checker to fail, it passed)" + FAIL=$((FAIL + 1)) + elif grep -q "FAIL:.*$needle" <<< "$got"; then + echo "ok - $name" + PASS=$((PASS + 1)) + else + echo "FAIL - $name (failed, but with no message matching '$needle')" + FAIL=$((FAIL + 1)) + fi +} + +expect_pass "a clean audit and an empty register pass" empty.reg clean.out +expect_pass "the registered yanked crate passes" valid.reg zip.out +expect_pass "an allowed warning (unsound) is not a finding here" empty.reg unsound.out + +expect_fail_with "a yanked crate with no register entry fails" empty.reg zip.out "zip 8.6.0 is yanked and is not in" +expect_fail_with "the same crate at another version fails" valid.reg zip-other-version.out "zip 8.6.1 is yanked and is not in" +expect_fail_with "and its old entry is then reported as stale" valid.reg zip-other-version.out "matches no yanked crate" +expect_fail_with "a second yanked crate beside the registered one fails" valid.reg two-zips.out "zip 8.6.1 is yanked and is not in" +expect_fail_with "an expired entry fails" expired.reg zip.out "expired on 2026-10-19" +expect_fail_with "an entry that outlasts 60 days fails" too-long.reg zip.out "runs past 2026-12-15" +expect_fail_with "an entry that expires before it was recorded fails" backwards.reg zip.out "before it was recorded" +expect_fail_with "an entry with a date that is not a date fails" bad-date.reg zip.out "real YYYY-MM-DD dates" +expect_fail_with "an entry with no owner fails" no-owner.reg zip.out "lacks: owner" +expect_fail_with "an entry with no release condition fails" no-release.reg zip.out "lacks: release-on" +expect_fail_with "an entry listed twice fails" twice.reg zip.out "appears twice" +expect_fail_with "a stale entry fails once the crate is no longer yanked" valid.reg clean.out "matches no yanked crate" + +expect_fail_with "a vulnerability fails" empty.reg vuln.out "wasmtime 49.0.2 has RUSTSEC-2026-0353" +expect_fail_with "a vulnerability fails even beside a registered yanked crate" valid.reg vuln-and-zip.out "wasmtime 49.0.2 has RUSTSEC-2026-0353" +expect_fail_with "an unmaintained crate fails" valid.reg unmaintained.out "paste 1.0.15 is unmaintained" +expect_fail_with "an audit that failed without a readable finding fails" empty.reg unreadable.out "without reporting a finding" +expect_fail_with "a count the checker cannot reproduce fails" valid.reg miscounted.out "denied 3 warning" + +echo +echo "$PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ]