diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index daef445..58639a1 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -37,6 +37,9 @@ jobs: --ignore RUSTSEC-2025-0119 --ignore RUSTSEC-2025-0134 --ignore RUSTSEC-2026-0002 + --ignore RUSTSEC-2026-0194 + --ignore RUSTSEC-2026-0195 + --ignore RUSTSEC-2026-0173 secrets: name: Secrets Scan diff --git a/.github/workflows/stability.yml b/.github/workflows/stability.yml index 52bc4d9..05af860 100644 --- a/.github/workflows/stability.yml +++ b/.github/workflows/stability.yml @@ -100,6 +100,9 @@ jobs: --ignore RUSTSEC-2025-0119 --ignore RUSTSEC-2025-0134 --ignore RUSTSEC-2026-0002 + --ignore RUSTSEC-2026-0194 + --ignore RUSTSEC-2026-0195 + --ignore RUSTSEC-2026-0173 deny: name: Cargo Deny @@ -108,4 +111,16 @@ jobs: - uses: actions/checkout@v5 - name: Checkout Reflective sibling dependencies run: bash scripts/ci/checkout-reflective-siblings.sh - - uses: EmbarkStudios/cargo-deny-action@v2 + # Not EmbarkStudios/cargo-deny-action: its docker container can't + # install the repo's pinned musl toolchain and doesn't see the + # sibling checkouts, so `cargo metadata` fails on converge-core. + # Run cargo-deny in the runner env like the audit job above. + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_VERSION }} + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-deny + - name: Run cargo-deny + run: cargo deny check diff --git a/Cargo.toml b/Cargo.toml index a8c4419..7834044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ parking_lot = "0.12" proptest = "1.5" lancedb = { version = "0.26", default-features = false } object_store = "0.13" -quick-xml = "0.39" +quick-xml = "0.41" regex-lite = "0.1" reqwest = { version = "0.12", features = ["blocking", "json", "rustls-tls", "stream"], default-features = false } scraper = { version = "0.20", default-features = false } diff --git a/Justfile b/Justfile index fe4389f..2fb7114 100644 --- a/Justfile +++ b/Justfile @@ -75,7 +75,10 @@ security-audit: --ignore RUSTSEC-2025-0119 \ --ignore RUSTSEC-2024-0436 \ --ignore RUSTSEC-2025-0134 \ - --ignore RUSTSEC-2026-0002 + --ignore RUSTSEC-2026-0002 \ + --ignore RUSTSEC-2026-0194 \ + --ignore RUSTSEC-2026-0195 \ + --ignore RUSTSEC-2026-0173 cargo deny check # Session opener diff --git a/crates/manifold/src/feed.rs b/crates/manifold/src/feed.rs index d632559..4e27ebc 100644 --- a/crates/manifold/src/feed.rs +++ b/crates/manifold/src/feed.rs @@ -723,13 +723,14 @@ fn read_text( reader: &mut Reader<&[u8]>, start: &BytesStart<'_>, ) -> Result, FeedError> { - reader + let text = reader .read_text(start.name()) - .map(|text| { - let trimmed = text.trim(); - (!trimmed.is_empty()).then_some(trimmed.to_string()) - }) - .map_err(|error| FeedError::Parse(error.to_string())) + .map_err(|error| FeedError::Parse(error.to_string()))?; + let text = text + .decode() + .map_err(|error| FeedError::Parse(error.to_string()))?; + let trimmed = text.trim(); + Ok((!trimmed.is_empty()).then_some(trimmed.to_string())) } fn local_name(name: &[u8]) -> String { @@ -744,7 +745,7 @@ fn attr_value(reader: &Reader<&[u8]>, start: &BytesStart<'_>, key: &[u8]) -> Opt .find(|attribute| local_name(attribute.key.as_ref()).as_bytes() == key) .and_then(|attribute| { attribute - .decode_and_unescape_value(reader.decoder()) + .decoded_and_normalized_value(quick_xml::XmlVersion::Implicit1_0, reader.decoder()) .ok() .map(std::borrow::Cow::into_owned) }) diff --git a/crates/manifold/src/model_selection.rs b/crates/manifold/src/model_selection.rs index 7aa6acd..ead543b 100644 --- a/crates/manifold/src/model_selection.rs +++ b/crates/manifold/src/model_selection.rs @@ -10,7 +10,11 @@ use serde::{Deserialize, Serialize}; -use crate::secret::{SecretProvider, default_secret_provider}; +use crate::secret::default_secret_provider; +// has_secret is a SecretProvider trait method; its call sites are all behind +// provider feature gates, so the import is unused in a featureless build. +#[allow(unused_imports)] +use crate::secret::SecretProvider as _; use converge_provider::LlmError; use converge_provider::selection::{ AgentRequirements, ComplianceLevel, CostClass, DataSovereignty, ModelSelectorTrait, diff --git a/crates/manifold/src/secret/mod.rs b/crates/manifold/src/secret/mod.rs index 8afc7ce..3b56b53 100644 --- a/crates/manifold/src/secret/mod.rs +++ b/crates/manifold/src/secret/mod.rs @@ -187,7 +187,7 @@ pub fn default_secret_provider() -> &'static DefaultSecretProvider { fn env_secrets_allowed() -> bool { matches!( std::env::var("MANIFOLD_ALLOW_ENV_SECRETS").ok().as_deref(), - Some("1") | Some("true") | Some("yes") + Some("1" | "true" | "yes") ) } diff --git a/crates/manifold/src/xml.rs b/crates/manifold/src/xml.rs index f775fdb..402eae3 100644 --- a/crates/manifold/src/xml.rs +++ b/crates/manifold/src/xml.rs @@ -46,6 +46,9 @@ pub fn extract_first_text(xml: &str, local_name: &str) -> Result, let text = reader .read_text(name) .map_err(|e| XmlExtractError::Parse(e.to_string()))?; + let text = text + .decode() + .map_err(|e| XmlExtractError::Parse(e.to_string()))?; return Ok(Some(text.into_owned())); } } @@ -83,6 +86,9 @@ pub fn extract_all_texts(xml: &str, local_name: &str) -> Result, Xml let text = reader .read_text(name) .map_err(|e| XmlExtractError::Parse(e.to_string()))?; + let text = text + .decode() + .map_err(|e| XmlExtractError::Parse(e.to_string()))?; out.push(text.into_owned()); } } diff --git a/deny.toml b/deny.toml index 8d21f17..3464db9 100644 --- a/deny.toml +++ b/deny.toml @@ -32,6 +32,20 @@ ignore = [ # lru 0.12.5 — IterMut unsoundness. Transitive via tantivy → lance-index; # we do not call IterMut on lru caches, exposure path is upstream-internal. "RUSTSEC-2026-0002", + # quick-xml 0.39.4 — duplicate-attribute quadratic run time (0194) and + # NsReader namespace-allocation DoS (0195), fixed in >=0.41. Our direct + # dep moved to 0.41 (2026-07-02); this pin is transitive via object_store + # 0.13.2 (latest available), which uses it to parse S3/Azure API list + # responses. Exposure is XML from the operator-configured storage + # provider's own API, not arbitrary attacker input, and both advisories + # are DoS-class (no memory unsafety). Drop both when object_store + # releases against quick-xml >=0.41. + "RUSTSEC-2026-0194", + "RUSTSEC-2026-0195", + # proc-macro-error2 2.0.1 unmaintained (advisory 2026-06-07). Transitive + # proc-macro, compile-time only, no runtime surface. No maintained + # upstream replacement path yet. Added 2026-07-02. + "RUSTSEC-2026-0173", ] [licenses] diff --git a/kb/Planning/MILESTONES.md b/kb/Planning/MILESTONES.md index 2ad0a9b..9d638f9 100644 --- a/kb/Planning/MILESTONES.md +++ b/kb/Planning/MILESTONES.md @@ -1,3 +1,6 @@ +> **Archived 2026-07-02** — active milestone tracking moved to Linear (Reflective team). +> This file is kept for historical context only. Do not add new items here. + --- source: mixed --- @@ -30,6 +33,7 @@ source: mixed - [x] Tag v1.0.0. ## Open: pull-driven +**Epic:** E9 - [ ] Downstream proof that products register Manifold handles through `converge_provider::ChatBackendRegistry`.