From dfb1055bbb8d39cbd24f7dac6eccf409590cd95a Mon Sep 17 00:00:00 2001 From: JunjoSick Date: Tue, 20 Oct 2026 17:47:52 +0200 Subject: [PATCH 1/5] fix(llm): route fetch_update through an MSRV-safe shim Rust 1.99 deprecates AtomicUsize::fetch_update in favor of try_update. CI runs floating stable with RUSTFLAGS="-D warnings", so since the runners moved to 1.99.0 on Oct 2 every stable job (clippy, test, test-windows, corpus) has failed to compile bookforge-llm. try_update is unstable on the 1.88 MSRV (E0658), so the compiler's suggested rename would break the msrv job instead. Both call sites now go through one private helper that keeps fetch_update behind a single allow(deprecated). Orderings and closures are unchanged. Co-Authored-By: Claude Opus 5.5 --- crates/bookforge-llm/src/concurrency.rs | 43 ++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/crates/bookforge-llm/src/concurrency.rs b/crates/bookforge-llm/src/concurrency.rs index b15816a2..5cb53750 100644 --- a/crates/bookforge-llm/src/concurrency.rs +++ b/crates/bookforge-llm/src/concurrency.rs @@ -120,12 +120,10 @@ impl Drop for AdaptivePermit { // Atomically claim a burn slot iff one is pending. If none is // pending, fetch_update returns Err and we fall through to the // normal Drop, which releases the permit back into the pool. - let claimed = self - .permits_to_burn - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| { - if n > 0 { Some(n - 1) } else { None } - }) - .is_ok(); + let claimed = fetch_update(&self.permits_to_burn, |n| { + if n > 0 { Some(n - 1) } else { None } + }) + .is_ok(); if claimed && let Some(permit) = self.permit.take() { permit.forget(); @@ -133,6 +131,19 @@ impl Drop for AdaptivePermit { } } +/// `AtomicUsize::fetch_update` with the orderings every caller here uses. +/// +/// Rust 1.99 deprecates `fetch_update` in favor of `try_update`, but +/// `try_update` is unstable on the 1.88 MSRV, so the deprecated method stays +/// behind this single allow until the MSRV can use the new name. +#[allow(deprecated)] +fn fetch_update( + atomic: &AtomicUsize, + update: impl FnMut(usize) -> Option, +) -> Result { + atomic.fetch_update(Ordering::AcqRel, Ordering::Acquire, update) +} + impl AdaptiveLimiter { pub fn new(min: usize, max: usize) -> Self { Self::new_with_progress(min, max, Duration::from_secs(2), None) @@ -235,18 +246,14 @@ impl AdaptiveLimiter { let mut remaining = new - previous; *state = new; while remaining > 0 { - match self.permits_to_burn.fetch_update( - Ordering::AcqRel, - Ordering::Acquire, - |burn| { - if burn == 0 { - None - } else { - let cancel = burn.min(remaining); - Some(burn - cancel) - } - }, - ) { + match fetch_update(&self.permits_to_burn, |burn| { + if burn == 0 { + None + } else { + let cancel = burn.min(remaining); + Some(burn - cancel) + } + }) { Ok(prev_burn) => { let cancelled = prev_burn.min(remaining); remaining -= cancelled; From bbe03a51d44c112fb198ad48f1a48bd4654f4a3b Mon Sep 17 00:00:00 2001 From: JunjoSick Date: Tue, 20 Oct 2026 17:47:54 +0200 Subject: [PATCH 2/5] fix(audio): drop needless return in the Windows publish path On Windows the cfg(not(windows)) block is compiled out, leaving the return as the tail of replace_file, which clippy's needless_return rejects. CI's clippy job runs on Linux and never compiles this block, so only a Windows-local -D warnings gate saw it. Behavior is unchanged. Co-Authored-By: Claude Opus 5.5 --- crates/bookforge-audio/src/atomic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bookforge-audio/src/atomic.rs b/crates/bookforge-audio/src/atomic.rs index e0efb5d5..5cb08a0f 100644 --- a/crates/bookforge-audio/src/atomic.rs +++ b/crates/bookforge-audio/src/atomic.rs @@ -29,7 +29,7 @@ pub fn replace_file(staged: &Path, destination: &Path) -> io::Result<()> { Err(_) => {} } } - return replace_file_windows(staged, destination); + replace_file_windows(staged, destination) } #[cfg(not(windows))] From 1b0e2396cf4aea4f5438c286af0953fb7a216cd0 Mon Sep 17 00:00:00 2001 From: JunjoSick Date: Tue, 20 Oct 2026 19:31:03 +0200 Subject: [PATCH 3/5] fix(deps): update rustls and quinn-proto for October advisories rustls 0.23.45 -> 0.23.46 resolves RUSTSEC-2026-0346/0347/0348/0349. quinn-proto 0.11.16 -> 0.11.19 resolves RUSTSEC-2026-0338 through 0342. quinn-proto is only reqwest's optional http3 dependency and is not compiled for any target here, but cargo audit reads every lockfile entry and RustSec is a required check on main. Lockfile-only: two version/checksum pairs, both within the 1.88 MSRV. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 774575fe..21929c9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1612,9 +1612,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.16" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" dependencies = [ "bytes", "getrandom 0.4.3", @@ -1900,9 +1900,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring", From 16c424b24ef218449c7df6b58744dcfe2dcbe59b Mon Sep 17 00:00:00 2001 From: JunjoSick Date: Tue, 20 Oct 2026 19:59:54 +0200 Subject: [PATCH 4/5] fix(deps): migrate to zip 9 for RUSTSEC-2026-0367/0368/0369 zip 8.6.0 carries three advisories against ZipArchive::extract and ZipStreamReader::extract. BookForge never calls either, but RustSec is a required check on main. zip 9.0.5 fixes them and declares rust-version 1.88, matching the MSRV; its new nix dependency is macOS/BSD-only. ZipFile::name() and ZipArchive::file_names() now return ZipResult>. Names decode lazily (UTF-8, then CP437, which maps every byte), so no name that 8.6 accepted is rejected; only an entry flagged UTF-8 with invalid bytes now decodes as CP437 instead of lossy UTF-8. validate.rs keeps its skip-unreadable-entry behavior. zip 9 also refuses to inflate an entry past its declared uncompressed size, so a lying entry is now rejected at declared size + 1 byte instead of at BookForge's per-entry limit. It reports that as a bare InvalidData I/O error; read_entry now re-raises InvalidData read errors as an EPUB decompression error naming the entry, like its other limit errors. Two tests assert the new message. The running-total test now uses honest entries with a directly built budget, since zip 9 refuses to read a lying entry at all. Adds a regression test for non-ASCII entry names through inspect, read and rebuild. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 17 +++++- Cargo.toml | 2 +- crates/bookforge-epub/src/archive_limits.rs | 23 ++++++-- crates/bookforge-epub/src/reader.rs | 4 +- crates/bookforge-epub/src/reflow.rs | 2 +- crates/bookforge-epub/src/validate.rs | 4 +- crates/bookforge-epub/src/writer.rs | 4 +- crates/bookforge-epub/tests/hostile_corpus.rs | 33 ++++++----- crates/bookforge-epub/tests/inspect.rs | 58 ++++++++++++++++++- 9 files changed, 114 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21929c9e..de8e0246 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,6 +1405,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -2934,14 +2946,15 @@ dependencies = [ [[package]] name = "zip" -version = "8.6.0" +version = "9.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" dependencies = [ "crc32fast", "flate2", "indexmap", "memchr", + "nix", "typed-path", ] diff --git a/Cargo.toml b/Cargo.toml index 7758e218..8da3156b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ quick-xml = { version = "0.42", features = ["escape-html"] } # pulling every codec (bzip2/zstd/lzma/aes-crypto chains incl. zstd-sys's C # build) and zip's optional `time`. The zlib-rs backend keeps flate2 pure-Rust # (no C toolchain) and matches the backend the old default features already used. -zip = { version = "8.6", default-features = false, features = ["deflate-flate2-zlib-rs"] } +zip = { version = "9.0", default-features = false, features = ["deflate-flate2-zlib-rs"] } # Held below 0.40 deliberately: 0.40 pulls a libsqlite3-sys that uses the # `cfg_select!` macro, which does not exist in our MSRV (see docs/msrv-audit.md # for why the floor is 1.88 and what it buys). Raising past 0.39 means raising diff --git a/crates/bookforge-epub/src/archive_limits.rs b/crates/bookforge-epub/src/archive_limits.rs index 51e2ab62..bd1e3e92 100644 --- a/crates/bookforge-epub/src/archive_limits.rs +++ b/crates/bookforge-epub/src/archive_limits.rs @@ -12,7 +12,7 @@ use std::{ fs::File, - io::{Read, Seek, SeekFrom}, + io::{ErrorKind, Read, Seek, SeekFrom}, path::Path, }; @@ -256,7 +256,16 @@ impl ArchiveReadBudget { // without allowing the decompressor or Vec to consume the rest of an attacker stream. let mut bounded = reader.take(read_limit.saturating_add(1)); let mut bytes = Vec::with_capacity(read_limit.min(64 * 1024) as usize); - bounded.read_to_end(&mut bytes)?; + bounded.read_to_end(&mut bytes).map_err(|error| { + // zip itself refuses to inflate an entry past its declared + // uncompressed size, reporting InvalidData before this budget + // trips. Name the entry, as the budget's own limit errors do. + if error.kind() == ErrorKind::InvalidData { + limit_error(format!("failed for '{name}': {error}")) + } else { + error.into() + } + })?; let bytes_read = bytes.len() as u64; if bytes_read > read_limit { @@ -304,7 +313,7 @@ pub fn validate_archive_metadata( let mut total_compressed = 0u64; for index in 0..archive.len() { let file = archive.by_index_raw(index)?; - let name = file.name().to_string(); + let name = file.name()?.into_owned(); let uncompressed = file.size(); let compressed = file.compressed_size(); if uncompressed > limits.max_entry_uncompressed_size { @@ -434,10 +443,12 @@ mod tests { .read_entry(&mut file, "liar.xhtml", compressed_size) .expect_err("bounded read must catch actual expansion"); + // zip stops at the declared 1 KiB, before the per-entry limit. + let message = error.to_string(); assert!( - error - .to_string() - .contains("per-entry uncompressed size limit exceeded for 'liar.xhtml'") + message.contains("EPUB decompression failed for 'liar.xhtml'") + && message.contains("declared uncompressed size"), + "{message}" ); } diff --git a/crates/bookforge-epub/src/reader.rs b/crates/bookforge-epub/src/reader.rs index 8cf706c0..cb96a2ca 100644 --- a/crates/bookforge-epub/src/reader.rs +++ b/crates/bookforge-epub/src/reader.rs @@ -308,8 +308,8 @@ pub fn inspect_epub(path: &Path) -> Result { // covers hostile archives). let archive_names = archive .file_names() - .map(|name| name.to_string()) - .collect::>(); + .map(|name| name.map(String::from)) + .collect::, _>>()?; let mut xhtml_spine_count = 0; for item in &package.spine { diff --git a/crates/bookforge-epub/src/reflow.rs b/crates/bookforge-epub/src/reflow.rs index 27ad4e63..7ffd17fc 100644 --- a/crates/bookforge-epub/src/reflow.rs +++ b/crates/bookforge-epub/src/reflow.rs @@ -177,7 +177,7 @@ fn write_archive_entries( ) -> Result<()> { for index in 0..archive.len() { let mut file = archive.by_index(index)?; - let name = file.name().to_string(); + let name = file.name()?.into_owned(); if name == "mimetype" { continue; diff --git a/crates/bookforge-epub/src/validate.rs b/crates/bookforge-epub/src/validate.rs index 111e7b87..e4e223fb 100644 --- a/crates/bookforge-epub/src/validate.rs +++ b/crates/bookforge-epub/src/validate.rs @@ -82,7 +82,9 @@ pub fn validate_translated_epub( let Ok(mut entry) = archive.by_index(index) else { continue; }; - let name = entry.name().to_string(); + let Ok(name) = entry.name().map(|name| name.into_owned()) else { + continue; + }; if !is_validatable_resource_name(&name) { file_names.push(name); continue; diff --git a/crates/bookforge-epub/src/writer.rs b/crates/bookforge-epub/src/writer.rs index 0bb61190..5ad977b7 100644 --- a/crates/bookforge-epub/src/writer.rs +++ b/crates/bookforge-epub/src/writer.rs @@ -150,7 +150,7 @@ fn write_rebuilt_epub( // A raw (metadata-only) handle answers cheap questions — name, // directory-ness, declared compression — before any bytes move. let raw = archive.by_index_raw(index)?; - let name = raw.name().to_string(); + let name = raw.name()?.into_owned(); if name == "mimetype" { continue; @@ -324,7 +324,7 @@ fn normalized_entry_options(compression_method: CompressionMethod) -> SimpleFile fn archive_entry_names(archive: &mut ZipArchive) -> Result> { let mut names = HashSet::new(); for index in 0..archive.len() { - names.insert(archive.by_index_raw(index)?.name().to_string()); + names.insert(archive.by_index_raw(index)?.name()?.into_owned()); } Ok(names) } diff --git a/crates/bookforge-epub/tests/hostile_corpus.rs b/crates/bookforge-epub/tests/hostile_corpus.rs index 170103ac..31bfd7fb 100644 --- a/crates/bookforge-epub/tests/hostile_corpus.rs +++ b/crates/bookforge-epub/tests/hostile_corpus.rs @@ -20,7 +20,9 @@ use std::{ use bookforge_core::{config::BilingualMode, segment::BlockTranslation}; use bookforge_epub::{ RebuildOptions, - archive_limits::{ArchiveLimits, read_archive_text, validate_archive_metadata}, + archive_limits::{ + ArchiveLimits, ArchiveReadBudget, read_archive_text, validate_archive_metadata, + }, inspect_epub, read_epub, rebuild_epub, rebuild_epub_with_options, text_coverage, }; use quick_xml::{Reader, events::Event}; @@ -236,8 +238,9 @@ fn injectable_limits_catch_declared_size_and_ratio_lies() { fn bounded_read_catches_declared_sizes_that_lie_about_expansion() { let dir = tmp_dir("bomb-read"); let epub = dir.join("lie.epub"); - // Stored payload expands far beyond what the central directory claims; - // with an injected 1 KiB cap the bounded read stops after 1 KiB + 1. + // Stored payload expands far beyond what the central directory claims. + // zip refuses to inflate past the declared 16 bytes, before the injected + // 1 KiB cap; the rejection must still name the entry. let big = vec![b'x'; 64 * 1024]; write_deflated_fixture(&dir.join("liar-src.zip"), "liar.bin", &big); relabel_declared_sizes(&dir.join("liar-src.zip"), &epub); @@ -255,9 +258,11 @@ fn bounded_read_catches_declared_sizes_that_lie_about_expansion() { let error = budget .read_entry(&mut file, "liar.bin", compressed) .expect_err("actual 64 KiB expansion must be caught"); + let message = error.to_string(); assert!( - error.to_string().contains("expanded data exceeds"), - "unexpected error: {error}" + message.contains("EPUB decompression failed for 'liar.bin'") + && message.contains("declared uncompressed size"), + "unexpected error: {message}" ); } @@ -293,10 +298,11 @@ fn relabel_declared_sizes(source: &Path, target: &Path) { #[test] fn bounded_budget_tracks_total_expansion_across_entries() { - // Two deflated entries whose central declarations say 16 bytes but that - // actually expand to 48 KiB each. The metadata stage passes (declared - // totals fit the tiny caps), so the running total is enforced at READ - // time: entry one fits, entry two crosses the remaining budget. + // Two deflated entries that expand to 48 KiB each against an 80 KiB + // running total, enforced at READ time: entry one fits, entry two crosses + // the remaining budget. zip rejects entries that inflate past their + // declared size, so these are honest and the budget is built directly; + // validate_archive_metadata would reject the declared 96 KiB upfront. let half: Vec = vec![b'z'; 48 * 1024]; let dir = tmp_dir("bomb-total"); let source = dir.join("half-src.zip"); @@ -308,10 +314,8 @@ fn bounded_budget_tracks_total_expansion_across_entries() { zip.start_file("b.bin", deflated).expect("start"); zip.write_all(&half).expect("write"); zip.finish().expect("finish"); - let epub = dir.join("liars.epub"); - relabel_declared_sizes(&source, &epub); - let mut archive = ZipArchive::new(File::open(&epub).expect("open")).expect("zip"); + let mut archive = ZipArchive::new(File::open(&source).expect("open")).expect("zip"); let limits = ArchiveLimits { max_entry_uncompressed_size: 64 * 1024, max_total_uncompressed_size: 80 * 1024, @@ -321,8 +325,7 @@ fn bounded_budget_tracks_total_expansion_across_entries() { ..tiny_limits(true) } }; - let mut budget = - validate_archive_metadata(&mut archive, limits).expect("16-byte declarations fit"); + let mut budget = ArchiveReadBudget::new(limits); for name in ["a.bin", "b.bin"] { let mut file = archive.by_name(name).expect("entry exists"); @@ -1215,7 +1218,7 @@ fn xml_string_api_preserves_unicode_entities_namespaces_and_archive_members() { assert_eq!(entry_bytes(&output, "mimetype"), MIMETYPE); let mut archive = ZipArchive::new(File::open(&output).unwrap()).unwrap(); let first = archive.by_index(0).unwrap(); - assert_eq!(first.name(), "mimetype"); + assert_eq!(first.name().unwrap(), "mimetype"); assert_eq!(first.compression(), CompressionMethod::Stored); } diff --git a/crates/bookforge-epub/tests/inspect.rs b/crates/bookforge-epub/tests/inspect.rs index e64d5966..5889dcb2 100644 --- a/crates/bookforge-epub/tests/inspect.rs +++ b/crates/bookforge-epub/tests/inspect.rs @@ -126,6 +126,50 @@ fn rebuilds_epub_with_patched_xhtml_and_preserved_resources() { assert!(chapter.contains("Ciao da un EPUB minimo.")); } +#[test] +fn non_ascii_entry_names_resolve_through_inspect_read_and_rebuild() { + // zip 9 decodes entry names on access instead of at parse time; a UTF-8 + // name must still match its manifest href on every archive path. + let href = "capítulo-東京.xhtml"; + let fixture = + create_epub_fixture_with_chapter("non-ascii-name", href, "

Hello from chapter 1.

"); + + let inspection = inspect_epub(&fixture).expect("non-ASCII spine entry should be found"); + assert_eq!(inspection.xhtml_spine_count, 1); + + let book = read_epub(&fixture).expect("non-ASCII entry should parse into IR"); + let body_block = book + .blocks + .iter() + .find(|block| block_text(block) == "Hello from chapter 1.") + .expect("body paragraph should be extracted"); + + let output = std::env::temp_dir().join(format!( + "bookforge-rebuilt-non-ascii-{}.epub", + std::process::id() + )); + let _ = std::fs::remove_file(&output); + rebuild_epub( + &book, + &[BlockTranslation { + block_id: body_block.id.clone(), + text: "Ciao da un capitolo non ASCII.".to_string(), + }], + &output, + ) + .expect("EPUB with a non-ASCII entry should rebuild"); + + let mut archive = ZipArchive::new(File::open(&output).expect("rebuilt EPUB should exist")) + .expect("rebuilt EPUB should be a zip"); + let mut chapter = String::new(); + archive + .by_name(&format!("OEBPS/{href}")) + .expect("non-ASCII entry name should survive the rebuild") + .read_to_string(&mut chapter) + .expect("chapter should be UTF-8"); + assert!(chapter.contains("Ciao da un capitolo non ASCII.")); +} + #[test] fn packed_notes_extract_and_rebuild_at_matching_dom_paths() { let fixture = create_epub_fixture( @@ -434,6 +478,10 @@ fn block_text(block: &bookforge_core::ir::Block) -> String { } fn create_epub_fixture(name: &str, body: &str) -> PathBuf { + create_epub_fixture_with_chapter(name, "chapter1.xhtml", body) +} + +fn create_epub_fixture_with_chapter(name: &str, chapter_href: &str, body: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("clock should be after epoch") @@ -463,16 +511,20 @@ fn create_epub_fixture(name: &str, body: &str) -> PathBuf { .start_file("OEBPS/content.opf", deflated) .expect("package entry should start"); writer - .write_all(CONTENT_OPF.as_bytes()) + .write_all( + CONTENT_OPF + .replace("chapter1.xhtml", chapter_href) + .as_bytes(), + ) .expect("package should write"); writer .start_file("OEBPS/nav.xhtml", deflated) .expect("nav entry should start"); writer - .write_all(NAV_XHTML.as_bytes()) + .write_all(NAV_XHTML.replace("chapter1.xhtml", chapter_href).as_bytes()) .expect("nav should write"); writer - .start_file("OEBPS/chapter1.xhtml", deflated) + .start_file(format!("OEBPS/{chapter_href}"), deflated) .expect("chapter entry should start"); writer .write_all(chapter_xhtml(body).as_bytes()) From 85988734a79660ad8c68ad7f4d3d6bd1fc6dc0be Mon Sep 17 00:00:00 2001 From: JunjoSick Date: Wed, 21 Oct 2026 01:17:26 +0200 Subject: [PATCH 5/5] fix(deps): update rustls-webpki and webpki-roots for RUSTSEC-2026-0381/0382 Both advisories were published after this branch's audit last passed: rustls-webpki 0.103.15 could accept distrusted intermediates (RUSTSEC-2026-0382) and webpki-roots 1.0.8 bundled roots that trust distrusted emSign intermediates (RUSTSEC-2026-0381). Lockfile-only: rustls-webpki 0.103.16 and webpki-roots 1.0.10, both within the 1.88 MSRV. cargo audit --deny warnings passes. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de8e0246..92e05258 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1936,9 +1936,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "ring", "rustls-pki-types", @@ -2732,9 +2732,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" dependencies = [ "rustls-pki-types", ]