From 1db2811773b78c26a42a3fd37f55ad818dac1faf Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Wed, 29 Jul 2026 20:51:02 +1000 Subject: [PATCH 1/3] fix(fuzz): bump fuzz workspace to core 0.4.0, run all 14 targets, fail loudly (LAB-1136) The fuzz workspace resolved deps independently of the parent crate (own [workspace]), so the core 0.4.0 bump never reached it: every fuzz iteration since has exercised the old 0.2.1 array-of-ints codec, and the msgpack bin envelope decode path had zero fuzz coverage. - rust/fuzz/Cargo.toml: cachekit-core =0.2.1 -> =0.4.0 (same features) - Fix 0.3.0 StorageEnvelope::new(Vec) -> new(&[u8]) at four call sites (checksum_collision, format_injection x2, layered_security) - Update two stale error-message assertions to 0.4.0's reworded variants ('integrity check failed', 'input exceeds maximum size'); the invariants themselves (corruption rejected, size limits enforced) still hold - fuzz-smoke.yml: build all targets once, run every target from 'cargo fuzz list' (no hand-maintained allow-list to rot), and let any non-zero build/run exit fail the job - the old .fuzz_failures marker was written but never read, so a broken target could not turn the job red. Timeout 20 -> 45 min (14 targets x 60s = 14 min fuzzing plus ~20 min toolchain install + ASAN builds on an uncached runner). - security-deep.yml: the extended-fuzz steps passed --features compression,checksum to a crate with no [features] table, so cargo errored before fuzzing and '|| true' swallowed it - the nominal 3x1h job was a no-op. Drop the bogus flags, replace 'timeout 3600 ... || true' with '-- -max_total_time=3600' so a clean hour exits 0 and a build error or crash fails the job. - Delete stale comments citing core 0.1.1 and closed #114 --- .github/workflows/fuzz-smoke.yml | 39 +++++++++---------- .github/workflows/security-deep.yml | 15 ++++--- rust/fuzz/Cargo.toml | 2 +- .../byte_storage_checksum_collision.rs | 4 +- .../byte_storage_format_injection.rs | 4 +- .../byte_storage_integer_overflow.rs | 2 +- .../integration_layered_security.rs | 2 +- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 101b5cf..018af41 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -27,7 +27,10 @@ jobs: fuzz-smoke: name: Fuzz Smoke Test (60s per target) runs-on: cachekit - timeout-minutes: 20 + # Budget: ~2 min rustup + ~10 min cargo-fuzz install (CARGO_HOME=/tmp/cargo, + # so no cross-run cache) + ~10 min one-shot ASAN build of all targets + # + 14 targets x 60 s fuzzing = ~14 min + slack. + timeout-minutes: 45 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -42,6 +45,13 @@ jobs: - name: Install cargo-fuzz run: cargo install --locked cargo-fuzz + - name: Build all fuzz targets + run: | + cd rust/fuzz + # One cargo invocation builds every [[bin]] target, sharing dependency + # compilation. A target that fails to compile fails the job here. + cargo +nightly-2026-04-27 fuzz build + - name: Run fuzzing smoke tests id: fuzz run: | @@ -50,28 +60,17 @@ jobs: # Create artifacts directory mkdir -p artifacts - # Fuzz targets that compile against cachekit-core 0.1.1. - # 9 encryption/advanced targets are disabled — cachekit-core API - # changed (encrypt_aes_gcm → encrypt_with_keys etc.) and the - # fuzz targets haven't been updated. See #114. - FUZZ_TARGETS=( - byte_storage_compress - byte_storage_decompress - byte_storage_format_injection - encryption_key_derivation - ) - - for target in "${FUZZ_TARGETS[@]}"; do + # Every [[bin]] target in fuzz/Cargo.toml runs — the list is derived, + # not hand-maintained, so new targets can't silently go dark. + # A non-zero exit (crash found, or target failed to run) fails the + # job immediately: green must mean "fuzzed and clean". + for target in $(cargo +nightly-2026-04-27 fuzz list); do echo "Fuzzing $target..." - - if ! cargo +nightly-2026-04-27 fuzz run "$target" -- -max_total_time=60; then - echo "::warning::Fuzz target '$target' found potential issues" - # Continue to test other targets even if one fails - touch artifacts/.fuzz_failures - fi + cargo +nightly-2026-04-27 fuzz run "$target" -- -max_total_time=60 done - # Check if any crashes were found + # Belt-and-braces: fail on any crash artifact even if the runs above + # all exited zero. if find artifacts -name 'crash-*' -o -name 'timeout-*' -o -name 'oom-*' | grep -q .; then echo "::error::Fuzzing discovered crashes or errors. See artifacts for details." exit 1 diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index 95cc958..a818070 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -70,23 +70,26 @@ jobs: - name: Install cargo-fuzz run: cargo install --locked cargo-fuzz + # The fuzz crate (cachekit-storage-fuzz) declares no [features] table — its + # cachekit-core feature set is fixed in rust/fuzz/Cargo.toml. Passing + # --features here makes cargo error out before any fuzzing happens, and the + # old `|| true` swallowed exactly that for three core versions. A non-zero + # exit (build failure or crash) must fail the job; a clean hour exits 0 via + # -max_total_time. - name: Fuzz byte_storage_compress (1 hour) run: | cd rust - timeout 3600 cargo fuzz run byte_storage_compress --no-default-features --features compression,checksum || true + cargo fuzz run byte_storage_compress -- -max_total_time=3600 - name: Fuzz byte_storage_decompress (1 hour) run: | cd rust - timeout 3600 cargo fuzz run byte_storage_decompress --no-default-features --features compression,checksum || true + cargo fuzz run byte_storage_decompress -- -max_total_time=3600 - # NOTE: encryption_roundtrip and 8 other encryption targets are stale against - # cachekit-core 0.1.1 (encrypt_aes_gcm → encrypt_with_keys). See #114. Using - # encryption_key_derivation, which compiles, until those targets are migrated. - name: Fuzz encryption_key_derivation (1 hour) run: | cd rust - timeout 3600 cargo fuzz run encryption_key_derivation --no-default-features --features encryption || true + cargo fuzz run encryption_key_derivation -- -max_total_time=3600 - name: Check for crashes run: | diff --git a/rust/fuzz/Cargo.toml b/rust/fuzz/Cargo.toml index 618cd3b..50099b1 100644 --- a/rust/fuzz/Cargo.toml +++ b/rust/fuzz/Cargo.toml @@ -18,7 +18,7 @@ rmp-serde = "1" # not the thin PyO3 wrapper's flat re-exports. [dependencies.cachekit_storage] package = "cachekit-core" -version = "=0.2.1" +version = "=0.4.0" features = ["compression", "checksum", "messagepack", "encryption"] # Prevent this from interfering with normal build diff --git a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs index 2405655..21c9c1b 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs @@ -24,7 +24,7 @@ fuzz_target!(|test_case: ChecksumTestCase| { } // Create valid envelope first - let envelope = match StorageEnvelope::new(test_case.data.clone(), "msgpack".to_string()) { + let envelope = match StorageEnvelope::new(&test_case.data, "msgpack".to_string()) { Ok(env) => env, Err(_) => return, // Skip if data too large }; @@ -59,7 +59,7 @@ fuzz_target!(|test_case: ChecksumTestCase| { // Expected: Checksum validation should fail let err_msg = err.to_string(); assert!( - err_msg.contains("Checksum validation failed") || err_msg.contains("decompression failed"), + err_msg.contains("integrity check failed") || err_msg.contains("decompression failed"), "Error should indicate checksum or decompression failure: {}", err_msg ); diff --git a/rust/fuzz/fuzz_targets/byte_storage_format_injection.rs b/rust/fuzz/fuzz_targets/byte_storage_format_injection.rs index 2865914..c2ceae6 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_format_injection.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_format_injection.rs @@ -17,7 +17,7 @@ fuzz_target!(|data: &[u8]| { // Create envelope with potentially malicious format let test_data = vec![b'x'; 100]; - let envelope = match StorageEnvelope::new(test_data, format.clone()) { + let envelope = match StorageEnvelope::new(&test_data, format.clone()) { Ok(env) => env, Err(_) => return, // Skip if envelope creation fails (acceptable) }; @@ -50,7 +50,7 @@ fuzz_target!(|data: &[u8]| { for pattern in &injection_patterns { let pattern_data = vec![b'y'; 50]; - if let Ok(env) = StorageEnvelope::new(pattern_data, pattern.to_string()) { + if let Ok(env) = StorageEnvelope::new(&pattern_data, pattern.to_string()) { // Format stored as-is assert_eq!(env.format, *pattern); diff --git a/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs b/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs index 3be02cd..1c78a70 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs @@ -45,7 +45,7 @@ fuzz_target!(|test_case: OverflowTestCase| { // Verify error message is descriptive let err_msg = err.to_string(); assert!( - err_msg.contains("Security violation") || err_msg.contains("failed"), + err_msg.contains("exceeds") || err_msg.contains("failed"), "Error message should be descriptive: {}", err_msg ); diff --git a/rust/fuzz/fuzz_targets/integration_layered_security.rs b/rust/fuzz/fuzz_targets/integration_layered_security.rs index 211b561..9cebbc6 100644 --- a/rust/fuzz/fuzz_targets/integration_layered_security.rs +++ b/rust/fuzz/fuzz_targets/integration_layered_security.rs @@ -26,7 +26,7 @@ fuzz_target!(|data: &[u8]| { }; // Step 1: Create ByteStorage envelope (compression + checksum) - let envelope = match StorageEnvelope::new(plaintext.to_vec(), "msgpack".to_string()) { + let envelope = match StorageEnvelope::new(plaintext, "msgpack".to_string()) { Ok(env) => env, Err(_) => return, // Plaintext too large }; From 6df13f6124baa462a4beded26be3d697c1c11d61 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Wed, 29 Jul 2026 21:00:35 +1000 Subject: [PATCH 2/3] =?UTF-8?q?fix(fuzz):=20apply=20expert-panel=20finding?= =?UTF-8?q?s=20=E2=80=94=20kill=20residual=20silent-pass=20modes=20(LAB-11?= =?UTF-8?q?36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panel (bug-hunter, security, craftsman, catchphrase) on the first commit: - fuzz-smoke.yml: resolve 'cargo fuzz list' in a standalone assignment and fail on an empty list — 'for t in $(cmd)' does not propagate a failing $(cmd) under set -e, so a broken list would loop zero times and go green having fuzzed nothing [CRIT] - Commit rust/fuzz/Cargo.lock (was gitignored): with CARGO_HOME=/tmp every CI run freshly resolved all fuzz-workspace transitives to newest release on a persistent self-hosted runner [MAJ, CWE-829] - Pin cargo-fuzz --version 0.13.2 in both workflows (floating install on self-hosted runner; same pattern as kani-verifier) [MAJ] - security-deep.yml: timeout 200 -> 240 min with budget arithmetic (-max_total_time is pure fuzz time; old 'timeout 3600' capped wall time and the job never ran honestly anyway) [MAJ] - security-deep.yml: add if:failure() crash-artifact upload — a crash now fails the fuzz step itself, and without upload the reproducer died with the ephemeral runner [MAJ] - security-deep.yml: delete dead 'Generate coverage report' step (output never uploaded or read; reintroduced the || true swallow) - security-deep.yml: state the real reason for the 3-of-14 subset (time budget) now the stale #114 justification is gone - checksum_collision.rs:85: dead 'Checksum' arm -> 'integrity check failed' (no 0.4.0 message contains capital-C Checksum) - integer_overflow.rs: delete tautological message assert (every 0.4.0 error matched it); the invariant is no-panic on extreme sizes - fuzz/Cargo.toml: document why the exact =0.4.0 pin exists and that it must move with every parent-crate core bump --- .github/workflows/fuzz-smoke.yml | 18 +- .github/workflows/security-deep.yml | 31 +- rust/fuzz/.gitignore | 1 - rust/fuzz/Cargo.lock | 1076 +++++++++++++++++ rust/fuzz/Cargo.toml | 4 + .../byte_storage_checksum_collision.rs | 2 +- .../byte_storage_integer_overflow.rs | 14 +- 7 files changed, 1123 insertions(+), 23 deletions(-) create mode 100644 rust/fuzz/Cargo.lock diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 018af41..69efcc0 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -29,7 +29,8 @@ jobs: runs-on: cachekit # Budget: ~2 min rustup + ~10 min cargo-fuzz install (CARGO_HOME=/tmp/cargo, # so no cross-run cache) + ~10 min one-shot ASAN build of all targets - # + 14 targets x 60 s fuzzing = ~14 min + slack. + # + ~1 min per fuzz target (count derived from `cargo fuzz list`, ~14 min + # today) + slack. timeout-minutes: 45 steps: @@ -43,7 +44,9 @@ jobs: rustup default nightly-2026-04-27 - name: Install cargo-fuzz - run: cargo install --locked cargo-fuzz + # Version-pinned: a floating `cargo install` hands a hijacked cargo-fuzz + # release immediate code exec on the self-hosted runner. + run: cargo install --locked cargo-fuzz --version 0.13.2 - name: Build all fuzz targets run: | @@ -62,9 +65,18 @@ jobs: # Every [[bin]] target in fuzz/Cargo.toml runs — the list is derived, # not hand-maintained, so new targets can't silently go dark. + # Resolve the list in a standalone assignment: `for t in $(cmd)` does + # not propagate a failing $(cmd) under `set -e`, and an empty list + # would loop zero times and go green having fuzzed nothing. + TARGETS=$(cargo +nightly-2026-04-27 fuzz list) + if [ -z "$TARGETS" ]; then + echo "::error::cargo fuzz list returned no targets" + exit 1 + fi + # A non-zero exit (crash found, or target failed to run) fails the # job immediately: green must mean "fuzzed and clean". - for target in $(cargo +nightly-2026-04-27 fuzz list); do + for target in $TARGETS; do echo "Fuzzing $target..." cargo +nightly-2026-04-27 fuzz run "$target" -- -max_total_time=60 done diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index a818070..50da1a6 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -49,7 +49,10 @@ jobs: fuzzing: name: Extended Fuzzing (3 targets × 1h) runs-on: cachekit - timeout-minutes: 200 + # Budget: ~2 min rustup + ~10 min cargo-fuzz install (CARGO_HOME=/tmp/cargo, + # no cross-run cache) + ~10 min ASAN builds + 3 x 60 min fuzz time + # (-max_total_time is pure fuzz time, excluding builds) + slack. + timeout-minutes: 240 env: # Avoid EXDEV "cross-device link" errors when rustup stages a nightly # toolchain across overlay/hostPath boundaries on the ARC runner pod @@ -68,7 +71,10 @@ jobs: rustup default nightly-2026-04-27 - name: Install cargo-fuzz - run: cargo install --locked cargo-fuzz + # Version-pinned: a floating `cargo install` hands a hijacked cargo-fuzz + # release immediate code exec on the self-hosted runner. Same pattern as + # kani-verifier above and fuzz-smoke.yml. + run: cargo install --locked cargo-fuzz --version 0.13.2 # The fuzz crate (cachekit-storage-fuzz) declares no [features] table — its # cachekit-core feature set is fixed in rust/fuzz/Cargo.toml. Passing @@ -76,6 +82,10 @@ jobs: # old `|| true` swallowed exactly that for three core versions. A non-zero # exit (build failure or crash) must fail the job; a clean hour exits 0 via # -max_total_time. + # + # Deliberate 3-of-14 subset: time budget. These three cover the highest-value + # attack surfaces (compression bombs, envelope decode, key derivation); the + # full target set gets 60 s each on every PR via fuzz-smoke.yml. - name: Fuzz byte_storage_compress (1 hour) run: | cd rust @@ -102,13 +112,16 @@ jobs: fi echo "✅ No crashes found during fuzzing" - - name: Generate coverage report - run: | - cd rust - for target in byte_storage_compress byte_storage_decompress encryption_key_derivation; do - echo "=== Coverage for $target ===" - cargo fuzz coverage $target || true - done + # A crash now fails the fuzz step itself, killing the job before the check + # above — without this upload the reproducer dies with the ephemeral runner. + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: extended-fuzz-crash-artifacts + path: rust/fuzz/artifacts/ + retention-days: 30 + if-no-files-found: warn atheris-fuzzing: name: Atheris Python-Rust Fuzzing diff --git a/rust/fuzz/.gitignore b/rust/fuzz/.gitignore index b1d95e1..6cbee8c 100644 --- a/rust/fuzz/.gitignore +++ b/rust/fuzz/.gitignore @@ -13,7 +13,6 @@ coverage/ # Build artifacts target/ -Cargo.lock # Crash reports and triage output crash-*.txt diff --git a/rust/fuzz/Cargo.lock b/rust/fuzz/Cargo.lock new file mode 100644 index 0000000..ad530c6 --- /dev/null +++ b/rust/fuzz/Cargo.lock @@ -0,0 +1,1076 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cachekit-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aba1513135a7b92a124ad6983f7e80e5f5c78c4f9c74384079efa3fbf491eab" +dependencies = [ + "aes", + "aes-gcm", + "byteorder", + "bytes", + "cbindgen", + "generic-array", + "getrandom 0.2.17", + "hkdf", + "hmac", + "lz4_flex", + "ring", + "rmp-serde", + "serde", + "serde_bytes", + "sha2", + "thiserror", + "xxhash-rust", + "zeroize", +] + +[[package]] +name = "cachekit-storage-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "cachekit-core", + "libfuzzer-sys", + "rmp-serde", +] + +[[package]] +name = "cbindgen" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lz4_flex" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/fuzz/Cargo.toml b/rust/fuzz/Cargo.toml index 50099b1..dd8d630 100644 --- a/rust/fuzz/Cargo.toml +++ b/rust/fuzz/Cargo.toml @@ -16,6 +16,10 @@ rmp-serde = "1" # Fuzz targets use cachekit-core's internal module paths (byte_storage::, encryption::) # not the thin PyO3 wrapper's flat re-exports. +# Exact pin, deliberately stricter than the parent crate's caret requirement: +# this [workspace] resolves independently of rust/Cargo.toml, so the pin is the +# only thing guaranteeing we fuzz the same core version the wrapper ships. +# Bump it in the same diff as every parent-crate core bump. [dependencies.cachekit_storage] package = "cachekit-core" version = "=0.4.0" diff --git a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs index 21c9c1b..0e89c4b 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs @@ -82,7 +82,7 @@ fuzz_target!(|test_case: ChecksumTestCase| { Err(err) => { let err_msg = err.to_string(); assert!( - err_msg.contains("Checksum") || err_msg.contains("failed"), + err_msg.contains("integrity check failed") || err_msg.contains("decompression failed"), "Wrong checksum should be detected: {}", err_msg ); diff --git a/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs b/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs index 1c78a70..707da6e 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_integer_overflow.rs @@ -40,15 +40,11 @@ fuzz_target!(|test_case: OverflowTestCase| { // Decompression succeeded - envelope passed all validation checks // This should only happen for valid sizes within limits } - Err(err) => { - // Expected for oversized allocations (u32::MAX, beyond 512MB, etc.) - // Verify error message is descriptive - let err_msg = err.to_string(); - assert!( - err_msg.contains("exceeds") || err_msg.contains("failed"), - "Error message should be descriptive: {}", - err_msg - ); + Err(_) => { + // Expected for oversized allocations (u32::MAX, beyond 512MB, etc.). + // Every 0.4.0 ByteStorageError message matches a string check here, + // so asserting on the text would be a tautology — the invariant this + // target enforces is "no panic on extreme sizes", not the wording. } } From cdb146c31cb172b9f2824cbd83e26859f1332bab Mon Sep 17 00:00:00 2001 From: opus Date: Wed, 29 Jul 2026 22:26:35 +1000 Subject: [PATCH 3/3] fix(fuzz): close the residual false-green paths this PR left behind (LAB-1136) Second expert-panel pass (crypto/protocol gate). Each item below is a way the hardened gate could still assert less than it claims. Workflows: - Enforce the committed fuzz lockfile. `cargo fuzz build/run` have no --locked passthrough, so the lockfile added here was advisory to the only commands that consume it: on any manifest/lock drift cargo would silently re-resolve every transitive to newest-on-crates.io, on a self-hosted runner with an empty CARGO_HOME. A `cargo fetch --locked` step now fails loudly on drift. Without it the CWE-829 fix was a lockfile nobody checked. - Guard fuzz_targets/ vs [[bin]] parity. `cargo fuzz list` enumerates Cargo.toml stanzas, NOT files, so a new fuzz_targets/*.rs without its stanza is never built and never run and the job still goes green -- the same silent-dark mode the old hardcoded array caused. Nothing in cargo enforces agreement; assert it. The old comment claimed the derived list made new targets impossible to lose, which overstated what deriving from Cargo.toml actually buys. - Build before fuzzing in security-deep.yml, so a compile error surfaces in ~10 min as a build failure rather than three hours in as a fuzz-step error. Its budget comment already itemised a build step that did not exist. - Delete the "Check for crashes" step. With `|| true` gone a crash fails its own fuzz step and kills the job, so this step could only run in the no-crash case and print a reassuring green -- a named step structurally incapable of failing, which is precisely the manufactured evidence this PR set out to remove. It also ran `find artifacts` without creating the dir, swallowing the error. The fuzz steps' exit codes are the signal. - Cut PR crash-artifact retention 30d -> 3d. This repo is public and the job runs on pull_request, so the artifact is a working reproducer for an unfixed defect in the shipped compression/AES-GCM path, downloadable by anyone for a month. - Correct the nightly-pin comments: they justified the pin with "cargo-fuzz 0.13.1" while both jobs install 0.13.2 -- stale on arrival, in the PR that exists to kill stale comments. byte_storage_checksum_collision.rs: - Both Ok(_) arms asserted nothing, so the one bug this target exists to find -- a corrupted payload extracting successfully as *different* data -- passed silently. The real invariant is reject-or-return-original; assert it, and for the forged all-0xFF checksum assert the genuine checksum really was 0xFF. - Match error variants instead of Display text. Asserting on err.to_string() means a #[error(...)] reword in a core release fails this gate on a false crash; the sibling integer_overflow target already dropped that pattern. - Widen flip_byte_idx u8 -> u32: payloads run to 4096 bytes, so a u8 index confined every bit flip this target could ever generate to the first 256. - Comments said "Blake3"; core 0.4.0 uses xxHash3-64 ("19x faster than Blake3", byte_storage.rs:6) and checksum is [u8; 8]. Naming a cryptographic hash where a 64-bit non-cryptographic one lives invites an assumption of collision resistance this code does not have. rust/fuzz/Makefile: - `make quick`/`make deep` still ran every target with `|| true`, so the local counterpart of this gate swallowed build and run failures exactly as CI did; only crash artifacts could fail it. Now `|| exit 1` (pipefail is already set at the top of the file, so it propagates through the tee). - FUZZ_TARGETS was a hand-maintained 14-target list -- the same drift hazard removed from the workflow, three directories away. Derived from the [[bin]] stanzas now, recursively expanded so no cargo/grep runs for `make help`. Verified: cargo check --locked --all-targets clean on all 14 targets; cargo fetch --locked succeeds against the committed lock (so the new enforcement step passes rather than spuriously failing); both workflows parse and pass actionlint/shellcheck; Makefile derives the same 14 target names. --- .github/workflows/fuzz-smoke.yml | 37 +++++++-- .github/workflows/security-deep.yml | 46 +++++++----- rust/fuzz/Makefile | 16 ++-- .../byte_storage_checksum_collision.rs | 75 +++++++++++-------- 4 files changed, 110 insertions(+), 64 deletions(-) diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 69efcc0..a018662 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -38,8 +38,9 @@ jobs: - name: Install Rust nightly run: | - # Pin nightly: cargo-fuzz 0.13.1 → rustix uses rustc_layout_scalar_valid_range_* - # attributes reserved after nightly-2026-04-27. Last known-good date. + # Pin nightly: cargo-fuzz's rustix dependency uses + # rustc_layout_scalar_valid_range_* attributes reserved after + # nightly-2026-04-27. Last known-good date. rustup toolchain install nightly-2026-04-27 rustup default nightly-2026-04-27 @@ -48,6 +49,16 @@ jobs: # release immediate code exec on the self-hosted runner. run: cargo install --locked cargo-fuzz --version 0.13.2 + - name: Verify fuzz lockfile is current + run: | + cd rust/fuzz + # `cargo fuzz build/run` have no --locked passthrough, so the committed + # Cargo.lock is advisory to them: on any manifest/lock drift cargo would + # silently re-resolve every transitive to newest-on-crates.io, on a + # self-hosted runner. This is the only step that enforces the lock — + # it fails loudly on drift, and warms the empty CARGO_HOME besides. + cargo +nightly-2026-04-27 fetch --locked + - name: Build all fuzz targets run: | cd rust/fuzz @@ -63,8 +74,8 @@ jobs: # Create artifacts directory mkdir -p artifacts - # Every [[bin]] target in fuzz/Cargo.toml runs — the list is derived, - # not hand-maintained, so new targets can't silently go dark. + # Every target `cargo fuzz list` reports runs — no hand-maintained + # allow-list in this workflow to drift out of date. # Resolve the list in a standalone assignment: `for t in $(cmd)` does # not propagate a failing $(cmd) under `set -e`, and an empty list # would loop zero times and go green having fuzzed nothing. @@ -74,6 +85,18 @@ jobs: exit 1 fi + # `cargo fuzz list` enumerates the [[bin]] stanzas in Cargo.toml, NOT the + # files in fuzz_targets/. So a new fuzz_targets/*.rs added without its + # stanza is never built and never run, and this job still goes green — + # the same silent-dark mode the old hardcoded array caused. Nothing in + # cargo enforces that the two agree, so assert it here. + SRC_COUNT=$(find fuzz_targets -maxdepth 1 -name '*.rs' | wc -l) + LIST_COUNT=$(printf '%s\n' "$TARGETS" | wc -l) + if [ "$SRC_COUNT" -ne "$LIST_COUNT" ]; then + echo "::error::fuzz_targets/ holds $SRC_COUNT sources but Cargo.toml declares $LIST_COUNT [[bin]] targets — add the missing [[bin]] stanza so the new target actually runs" + exit 1 + fi + # A non-zero exit (crash found, or target failed to run) fails the # job immediately: green must mean "fuzzed and clean". for target in $TARGETS; do @@ -96,5 +119,9 @@ jobs: with: name: fuzz-crash-artifacts path: rust/fuzz/artifacts/ - retention-days: 30 + # Short window deliberately: this repo is public and this job runs on + # pull_request, so the artifact is a working libFuzzer reproducer for an + # unfixed defect in the shipped compression/AES-GCM path, downloadable by + # anyone. 3 days is enough to triage; 30 is a month-long public window. + retention-days: 3 if-no-files-found: warn diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index 50da1a6..46b074b 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -64,9 +64,9 @@ jobs: - name: Install nightly Rust run: | - # Pin nightly: cargo-fuzz 0.13.1 → rustix uses rustc_layout_scalar_valid_range_* - # attributes reserved after nightly-2026-04-27. Last known-good date. - # Same pin as fuzz-smoke.yml. + # Pin nightly: cargo-fuzz's rustix dependency uses + # rustc_layout_scalar_valid_range_* attributes reserved after + # nightly-2026-04-27. Last known-good date. Same pin as fuzz-smoke.yml. rustup toolchain install nightly-2026-04-27 rustup default nightly-2026-04-27 @@ -76,6 +76,20 @@ jobs: # kani-verifier above and fuzz-smoke.yml. run: cargo install --locked cargo-fuzz --version 0.13.2 + - name: Verify fuzz lockfile is current + run: | + cd rust/fuzz + # See fuzz-smoke.yml: cargo fuzz has no --locked passthrough, so this is + # the only step enforcing the committed lock against manifest drift. + cargo +nightly-2026-04-27 fetch --locked + + - name: Build fuzz targets + run: | + cd rust/fuzz + # Build before fuzzing so a compile error surfaces in ~10 min as a build + # failure, not three hours in as a confusing fuzz-step error. + cargo +nightly-2026-04-27 fuzz build + # The fuzz crate (cachekit-storage-fuzz) declares no [features] table — its # cachekit-core feature set is fixed in rust/fuzz/Cargo.toml. Passing # --features here makes cargo error out before any fuzzing happens, and the @@ -83,9 +97,9 @@ jobs: # exit (build failure or crash) must fail the job; a clean hour exits 0 via # -max_total_time. # - # Deliberate 3-of-14 subset: time budget. These three cover the highest-value - # attack surfaces (compression bombs, envelope decode, key derivation); the - # full target set gets 60 s each on every PR via fuzz-smoke.yml. + # Deliberate subset for the time budget: these three cover the highest-value + # attack surfaces (compression bombs, envelope decode, key derivation); every + # target gets 60 s on every PR via fuzz-smoke.yml. - name: Fuzz byte_storage_compress (1 hour) run: | cd rust @@ -101,19 +115,13 @@ jobs: cd rust cargo fuzz run encryption_key_derivation -- -max_total_time=3600 - - name: Check for crashes - run: | - cd rust/fuzz - CRASHES=$(find artifacts -name "crash-*" 2>/dev/null | wc -l) - if [ "$CRASHES" -gt 0 ]; then - echo "❌ Found $CRASHES crashes during fuzzing" - find artifacts -name "crash-*" -exec echo "Crash: {}" \; - exit 1 - fi - echo "✅ No crashes found during fuzzing" - - # A crash now fails the fuzz step itself, killing the job before the check - # above — without this upload the reproducer dies with the ephemeral runner. + # No "Check for crashes" step: now that `|| true` is gone, a crash fails its + # own fuzz step and kills the job, so a trailing check could only ever run in + # the no-crash case and print ✅ — a named green step incapable of failing, + # which is the exact manufactured-evidence pattern this PR removes. (It also + # ran `find artifacts` without creating the dir, swallowing the error.) The + # fuzz steps' exit codes are the signal; this upload preserves the evidence, + # which would otherwise die with the ephemeral runner. - name: Upload crash artifacts if: failure() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 diff --git a/rust/fuzz/Makefile b/rust/fuzz/Makefile index 422e4d1..cfa9ab2 100644 --- a/rust/fuzz/Makefile +++ b/rust/fuzz/Makefile @@ -23,12 +23,12 @@ $(shell mkdir -p $(LOG_FUZZ_DIR)) # Timestamp for log files (inherited from parent, or generated here) TIMESTAMP ?= $(shell date +%Y%m%d_%H%M%S) -# All fuzz targets (complete list) -FUZZ_TARGETS := byte_storage_compress byte_storage_decompress encryption_roundtrip \ - byte_storage_corrupted_envelope byte_storage_integer_overflow byte_storage_checksum_collision \ - byte_storage_empty_data byte_storage_format_injection encryption_key_derivation \ - encryption_nonce_reuse encryption_truncated_ciphertext encryption_aad_injection \ - encryption_large_payload integration_layered_security +# All fuzz targets, derived from the [[bin]] stanzas in Cargo.toml — the same +# source `cargo fuzz list` reads. Previously a hand-maintained list, i.e. the +# identical drift hazard the CI workflow just removed: a new target added here +# but not there (or vice versa) goes silently unfuzzed. Recursive `=`, not `:=`, +# so the grep runs only when a fuzzing target actually uses it. +FUZZ_TARGETS = $(shell grep -A1 '^\[\[bin\]\]' Cargo.toml | sed -n 's/^name = "\(.*\)"/\1/p') # Helper function to check if a binary exists define require_binary @@ -71,7 +71,7 @@ quick: ## Run quick fuzzing smoke test (60s per target, ~14min total) @{ \ for target in $(FUZZ_TARGETS); do \ echo "$(YELLOW)Fuzzing $$target (60s)...$(RESET)"; \ - cargo +nightly fuzz run $$target -- -max_total_time=60 || true; \ + cargo +nightly fuzz run $$target -- -max_total_time=60 || exit 1; \ done && \ echo "$(YELLOW)Checking for crashes...$(RESET)" && \ CRASHES=$$(find artifacts -name "crash-*" 2>/dev/null | wc -l) && \ @@ -92,7 +92,7 @@ deep: ## Run deep fuzzing (8 hours per target, production-grade) @{ \ for target in $(FUZZ_TARGETS); do \ echo "$(YELLOW)Deep fuzzing $$target (8 hours)...$(RESET)"; \ - cargo +nightly fuzz run $$target -- -max_total_time=28800 || true; \ + cargo +nightly fuzz run $$target -- -max_total_time=28800 || exit 1; \ done && \ echo "$(YELLOW)Checking for crashes...$(RESET)" && \ CRASHES=$$(find artifacts -name "crash-*" 2>/dev/null | wc -l) && \ diff --git a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs index 0e89c4b..88545a4 100644 --- a/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs +++ b/rust/fuzz/fuzz_targets/byte_storage_checksum_collision.rs @@ -1,22 +1,26 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use cachekit_storage::byte_storage::StorageEnvelope; +use cachekit_storage::byte_storage::{ByteStorageError, StorageEnvelope}; use arbitrary::Arbitrary; #[derive(Arbitrary, Debug)] struct ChecksumTestCase { /// Data to compress (will be used for valid envelope) data: Vec, - /// Bit flip position in compressed_data (0-255) - flip_byte_idx: u8, + /// Bit flip position in compressed_data, reduced mod payload length. + /// u32, not u8: payloads run to 4096 bytes, so a u8 index would confine + /// every flip this target ever generates to the first 256 bytes. + flip_byte_idx: u32, /// Bit flip mask flip_mask: u8, } fuzz_target!(|test_case: ChecksumTestCase| { // Attack: Checksum collision via data corruption - // Validates: Blake3 integrity verification detects mismatches + // Validates: the xxHash3-64 integrity check (8-byte, NON-cryptographic — + // it detects corruption, it does not resist a chosen-collision attacker) + // rejects a mutated payload, or else returns the original bytes unchanged. // Limit data size for fuzzing performance if test_case.data.len() > 4096 { @@ -49,21 +53,25 @@ fuzz_target!(|test_case: ChecksumTestCase| { corrupted_envelope.compressed_data[idx] ^= test_case.flip_mask; } - // Corrupted envelope should be rejected (checksum mismatch) + // The invariant is reject-or-return-original. Asserting only on the Err arm + // would let the one bug this target exists to find — corrupted bytes + // extracting *successfully* as different data — pass silently. match corrupted_envelope.extract() { - Ok(_) => { - // If it succeeded, data must be unchanged (flip reverted or no-op) - // This is only acceptable if flip_mask was 0 or flipped back to original - } - Err(err) => { - // Expected: Checksum validation should fail - let err_msg = err.to_string(); - assert!( - err_msg.contains("integrity check failed") || err_msg.contains("decompression failed"), - "Error should indicate checksum or decompression failure: {}", - err_msg - ); - } + Ok(recovered) => assert_eq!( + recovered, test_case.data, + "corrupted envelope extracted successfully but returned different data \ + — integrity check bypassed" + ), + Err(err) => assert!( + matches!( + err, + ByteStorageError::ChecksumMismatch | ByteStorageError::DecompressionFailed + ), + // Match the variant, not err.to_string(): asserting on Display text + // makes a #[error(...)] reword in a core release fail this gate on a + // false crash. + "corruption must surface as ChecksumMismatch or DecompressionFailed, got: {err:?}" + ), } // Test with completely wrong checksum @@ -74,21 +82,24 @@ fuzz_target!(|test_case: ChecksumTestCase| { format: envelope.format.clone(), }; - // Should be rejected unless original checksum happened to be all 0xFF + // Should be rejected unless the genuine checksum happened to be all 0xFF — + // which is the only case the Ok arm may accept, so assert exactly that. match wrong_checksum_envelope.extract() { - Ok(_) => { - // Only acceptable if original checksum was [0xFF; 8] - } - Err(err) => { - let err_msg = err.to_string(); - assert!( - err_msg.contains("integrity check failed") || err_msg.contains("decompression failed"), - "Wrong checksum should be detected: {}", - err_msg - ); - } + Ok(_) => assert_eq!( + envelope.checksum, [0xFF; 8], + "forged all-0xFF checksum accepted over a payload whose real checksum \ + was {:?} — integrity check bypassed", + envelope.checksum + ), + Err(err) => assert!( + matches!( + err, + ByteStorageError::ChecksumMismatch | ByteStorageError::DecompressionFailed + ), + "forged checksum must surface as ChecksumMismatch or DecompressionFailed, got: {err:?}" + ), } - // Success: Checksum validation detects corruption - // Invariant: Blake3 integrity must catch data tampering + // Invariant: the xxHash3-64 integrity check must catch data tampering — a + // mutated payload either fails to extract or yields the original bytes. });