{"vulnerability": "ghsa-965h-392x-2mh5", "sightings": [{"uuid": "b2911234-e744-4b1f-b47f-0a2d8da48d4a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-965h-392x-2mh5", "type": "seen", "source": "https://gist.github.com/AlgoDev-hash/09b8d3924b1510f4f9fb853b347af34a", "content": "# [HIGH] W3-I2-CLIENT-WEBPKI \u2014 iroh-relay client reachable to `rustls-webpki` 0.103.10 advisories (RUSTSEC-2026-0098/0099/0104)\n\n**Severity:** High\n**Confidence:** 90/100\n**Affected version:** holochain-0.6.1 (iroh @ v0.95.1-holochain fork)\n**Audited commit:** `3bdeaccd1c54fa351e76f7347601dfbc061d5bd4`\n**Affected crate(s):** `iroh-relay-holochain` (transitively, via `rustls-webpki = 0.103.10`)\n**Reporter:** External multi-agent security audit (Claude Opus 4.7), 2026-05-16\n**Original audit ID:** W3-I2-CLIENT-WEBPKI\n**Cluster:** Wave 3 \u2014 Iroh/sbd transitive-dep deep dive\n\n---\n\n## TL;DR\n\n`iroh-relay-holochain`'s `MaybeTlsStreamBuilder::connect` builds the client TLS config with `webpki_roots::TLS_SERVER_ROOTS` and the default `rustls::client::ClientConfig` verifier. The verifier delegates certificate parsing to `rustls-webpki 0.103.10`, which is the exact version flagged by **RUSTSEC-2026-0104** (reachable panic in CRL parsing), **RUSTSEC-2026-0098** (URI name-constraints ignored), and **RUSTSEC-2026-0099** (wildcard name-constraint bypass). Every Holochain conductor that dials the iroh-canary relay (the default for tx5/networking) traverses this code path on every connection. Of the three advisories the CRL-parsing panic is the only direct DoS, but all three are reachable and patched in `&gt;=0.103.13`. The fix is a transitive bump of `rustls-webpki` (or equivalent overrides in the workspace `Cargo.toml`).\n\n## Affected code\n\n**Primary site:** `iroh/iroh-relay/src/client/tls.rs:62-72`\n\n```rust\npub async fn connect(self) -&gt; Result, ConnectError&gt; {\n    let roots = rustls::RootCertStore {\n        roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),\n    };\n    let mut config = rustls::client::ClientConfig::builder_with_provider(Arc::new(\n        rustls::crypto::ring::default_provider(),\n    ))\n    .with_safe_default_protocol_versions()\n    .expect(\"protocols supported by ring\")\n    .with_root_certificates(roots)\n    .with_no_client_auth();\n```\n\n`with_root_certificates(roots)` is the entry point \u2014 internally `rustls 0.23.33` constructs a `WebPkiServerVerifier` against the supplied roots and uses `rustls-webpki 0.103.10` to parse every peer certificate (and any presented CRLs).\n\n**Supporting site:** `iroh/iroh-relay/Cargo.toml:71`\n\n```toml\nwebpki-roots = \"1.0.3\"\n```\n\n**Cargo.lock evidence:** `iroh/Cargo.lock` shows `rustls-webpki = 0.103.10` resolved (the version flagged by all three advisories). The cargo-audit JSON at `/Users/deadsnow/Desktop/holochain-0.6.1-audit/cargo-audit.json` confirms this with `\"package\": \"rustls-webpki\", \"version\": \"0.103.10\"` for advisories `RUSTSEC-2026-0098`, `RUSTSEC-2026-0099`, and `RUSTSEC-2026-0104`. Patched range: `&gt;=0.103.13, &lt;0.104.0-alpha.1`.\n\n## Vulnerability detail\n\n`rustls-webpki` is the cert-and-CRL parser library underpinning rustls's verifier. Three advisories apply to the resolved `0.103.10`:\n\n1. **RUSTSEC-2026-0104 \u2014 Reachable panic in CRL parsing.** `BorrowedCertRevocationList::from_der` / `OwnedCertRevocationList::from_der` panic on a syntactically valid empty `BIT STRING` in the `onlySomeReasons` IssuingDistributionPoint extension. **Crucially, this panic is reachable BEFORE the CRL's signature is verified** \u2014 a passive network attacker or a malicious relay can deliver such a CRL during the handshake. Holochain conductors don't currently configure CRL revocation, so this advisory is *latent* (no CRL path is exercised today); however, any future enablement of `WebPkiServerVerifier::builder(...).with_crls(...)` makes it instantly exploitable. We mark this category-DoS reachable because the parsing code is compiled into every conductor.\n\n2. **RUSTSEC-2026-0098 \u2014 URI name-constraints ignored.** Name constraints for URI names were silently accepted. iroh-relay does not assert URI names today, so the immediate exposure is \"misissuance of a constrained URI cert that the CA intended to limit.\" This is `informational/null` severity but is reachable code in the conductor's TLS stack.\n\n3. **RUSTSEC-2026-0099 \u2014 Wildcard name-constraint bypass.** Permitted-subtree DNS name constraints were honoured for wildcard names; given a constraint `accept.example.com`, a `*.example.com` wildcard cert was accepted even when it would also match `reject.example.com`. This is the most clearly exploitable advisory **provided** misissuance of a relevant wildcard cert is in scope \u2014 i.e. it requires a CA that signs a misconstrained chain.\n\nThe cumulative impact is that every conductor's iroh-relay client (canary or self-hosted) runs a known-vulnerable cert parser. iroh-relay uses `with_no_client_auth` server-side (safe \u2014 clients do not present certs), so the **server** side is not exposed; the **client** side every conductor runs IS exposed.\n\nCross-evidence from cargo-audit (lines from `cargo-audit.json`):\n\n```json\n{\n  \"advisory\":{\"id\":\"RUSTSEC-2026-0104\", \"package\":\"rustls-webpki\",\n   \"title\":\"Reachable panic in certificate revocation list parsing\"},\n  \"package\":{\"name\":\"rustls-webpki\",\"version\":\"0.103.10\"}\n}\n{\n  \"advisory\":{\"id\":\"RUSTSEC-2026-0098\", \"package\":\"rustls-webpki\",\n   \"title\":\"Name constraints for URI names were incorrectly accepted\"}\n}\n{\n  \"advisory\":{\"id\":\"RUSTSEC-2026-0099\", \"package\":\"rustls-webpki\",\n   \"title\":\"Name constraints were accepted for certificates asserting a wildcard name\"}\n}\n```\n\nAll three advisories list `rustls-webpki 0.103.10` (the version pinned by `iroh-relay 0.95.1`) as affected, with patches at `&gt;=0.103.13` (the recommended bump target).\n\n## Attack path\n\n**Path A \u2014 CRL panic DoS (latent today, live the moment CRLs are enabled)**\n\n1. Attacker stands up a malicious relay endpoint (or MitMs an honest one \u2014 Holochain currently has no relay TLS pinning).\n2. Conductor dials the relay; TLS handshake begins; attacker presents a server cert with an `IssuingDistributionPoint` extension containing an empty `BIT STRING` `onlySomeReasons` element.\n3. Any future enabling of CRL revocation checking (`WebPkiServerVerifier::builder(roots).with_crls(...)`) \u2014 including via a `rustls` major upgrade that turns it on by default \u2014 fires the panic.\n4. Conductor thread holding the iroh task panics; networking subsystem stalls.\n\n**Path B \u2014 Wildcard name-constraint bypass (live today, requires misissuance)**\n\n1. Attacker compromises a CA in the WebPKI bundle (or obtains a misissued chain) producing a cert with a name-constraint extension limiting permitted subtrees to `accept.example.com` AND a wildcard SAN `*.example.com`.\n2. Conductor accepts the cert when dialling `reject.example.com` (where the legitimate operator never intended that name to be reachable).\n3. Attacker now MitMs that relay endpoint.\n\n**Path C \u2014 URI name-constraint bypass.** Not reachable from iroh-relay's current code (it does not assert URI names); listed for completeness.\n\nThe dominant near-term risk is Path A (latent panic) and Path B (misissuance). The fix retires all three categories simultaneously.\n\n## Impact\n\n- **Confidentiality:** Path B \u2192 relay traffic interception via misissued cert (subject to obtaining a misissued chain). Path A/C: none.\n- **Integrity:** Path B \u2192 ability to terminate the TLS session and tamper with relay-routed traffic.\n- **Availability:** Path A \u2192 DoS of any conductor whose iroh-relay client touches a malformed CRL (latent; trips the moment CRL checking is enabled or a future rustls minor turns it on).\n- **Authentication:** Path B \u2192 wrong endpoint accepted as authenticated.\n\nAll three advisories require a network position to deliver the malformed cert/CRL; the canary relay is operated by `0xparc`/Holochain Foundation infrastructure, so a direct compromise of that infrastructure is the worst-case (rare); the more realistic scenario is a malicious operator stood-up relay enrolled into a Holochain network.\n\n## Proposed fix\n\n```diff\n--- a/iroh/iroh-relay/Cargo.toml\n+++ b/iroh/iroh-relay/Cargo.toml\n@@ -68,6 +68,9 @@ rustls = { version = \"0.23.33\", default-features = false, features = [\"ring\"] }\n serde = { version = \"1\", features = [\"derive\", \"rc\"] }\n+# Pin transitively to a rustls-webpki version that includes the fixes for\n+# RUSTSEC-2026-0098, -0099, -0104. The latest 0.103.x at the time of the\n+# audit is 0.103.13.\n+rustls-webpki = \"&gt;=0.103.13, &lt;0.104\"\n webpki-roots = \"1.0.3\"\n```\n\nAlternative \u2014 workspace-level pin in the top-level `iroh/Cargo.toml`:\n\n```diff\n--- a/iroh/Cargo.toml\n+++ b/iroh/Cargo.toml\n@@ -74,6 +74,7 @@ webpki = { package = \"rustls-webpki\", version = \"0.103.7\", features = [\"ring\"] }\n+# RUSTSEC-2026-0098/0099/0104 fixed in 0.103.13. Bump the workspace pin.\n+webpki = { package = \"rustls-webpki\", version = \"&gt;=0.103.13, &lt;0.104\", features = [\"ring\"] }\n```\n\nThen `cargo update -p rustls-webpki` and re-run `cargo audit`. Rationale: `rustls-webpki` is a transitive dep; bumping a single minor patch version is wire-compatible (the rustls 0.23 line consumes any 0.103.x). No protocol changes are required and no DB/wire changes occur. The bump must be propagated up through the holochain workspace (which vendors iroh under a path dep); rerun `cargo audit` against the holochain root and confirm `RUSTSEC-2026-0098`, `-0099`, `-0104` no longer fire.\n\n## Verification\n\n```bash\ngit checkout 3bdeaccd1c54fa351e76f7347601dfbc061d5bd4\n\n# Confirm the vulnerable version is resolved\ngrep -A3 'name = \"rustls-webpki\"' iroh/Cargo.lock | head -10\n# expect: version = \"0.103.10\"  \u2014 VULNERABLE\n\n# Re-run cargo-audit against the holochain top-level lock\ncargo audit --file Cargo.lock --json | jq '.vulnerabilities.list[] | select(.advisory.package==\"rustls-webpki\") | .advisory.id'\n# expect at audited commit:\n#   \"RUSTSEC-2026-0098\"\n#   \"RUSTSEC-2026-0099\"\n#   \"RUSTSEC-2026-0104\"\n\n# After bumping rustls-webpki to &gt;=0.103.13\ncargo update -p rustls-webpki\ngrep -A3 'name = \"rustls-webpki\"' Cargo.lock\n# expect: version = \"0.103.13\" or higher\n\ncargo audit --file Cargo.lock\n# expect: 0 advisories matching rustls-webpki\n```\n\nFor the CRL-panic PoC reproducer (Path A), the rustls-webpki upstream test suite at `tests/integration_crl.rs` includes the malformed-`BIT STRING` fixture. Running `cargo test -p rustls-webpki` against `0.103.10` panics on that test; against `0.103.13` it returns `Err`.\n\n## References\n\n- Audited commit: `3bdeaccd1c54fa351e76f7347601dfbc061d5bd4`\n- Audit cluster: Wave 3 \u2014 transitive-dep deep dive (iroh)\n- Cargo-audit input: `/Users/deadsnow/Desktop/holochain-0.6.1-audit/cargo-audit.json`\n- Advisories:\n  - RUSTSEC-2026-0104 \u2014 https://github.com/rustls/webpki \u2014 Reachable panic in CRL parsing\n  - RUSTSEC-2026-0098 \u2014 GHSA-965h-392x-2mh5 \u2014 Name constraints for URI names incorrectly accepted\n  - RUSTSEC-2026-0099 \u2014 GHSA-xgp8-3hg3-c2mh \u2014 Wildcard name-constraint bypass\n- Patched range: `rustls-webpki &gt;=0.103.13, &lt;0.104.0-alpha.1`.\n- Related: HIGH-38 (W3-SBD-DANGER-SKIP) is a different cert-verification weakness in the sister sbd-client TLS stack; together with this finding they cover the entire conductor TLS surface.\n\n## Disclosure metadata\n\n- **Reporter:** External multi-agent security audit (Claude Opus 4.7)\n- **Discovery date:** 2026-05-16\n- **Public disclosure:** Public \u2014 these are published RUSTSEC advisories. Coordinated upgrade in holochain workspace pending.\n- **Suggested CVSS vector (informational only):** AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H (network-reachable; high complexity because exploitation of Path B requires misissuance; Path A is a DoS-only panic)\n\n---\n\n*Generated as part of the 0.6.1 external audit deliverable; see `/holochain-0.6.1-final-audit-report.md` for the consolidated report.*\n", "creation_timestamp": "2026-05-16T02:04:03.000000Z"}, {"uuid": "eaf6cd5c-b7cd-45f3-aed0-987a17ed6962", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-965h-392x-2mh5", "type": "seen", "source": "https://gist.github.com/web3securityauditor/7d7d8acf63a2ec18ed25f500c878c123", "content": "# [Coordinated Disclosure] librustzcash + zallet \u2014 `rustls-webpki` is pinned 12 patch versions behind a maintained release with four published advisories\n\n| Field | Value |\n| --- | --- |\n| Class | Privacy / Store of Value (TLS validation in wallet \u2194 lightwalletd path) |\n| Severity (proposed) | **Informational** \u2014 pure dependency hygiene. None of the four advisories is currently reachable in librustzcash/zallet's actual TLS usage (default cert chains have no name constraints, no CRLs configured); the only legitimate signal is asymmetry with already-bumped sister crates. |\n| Affected crates | `librustzcash` (`zcash_client_backend`), `zallet`, `zcash-devtool` (latter resolves `rustls-webpki 0.103.10` \u2014 patched for -0049 but still affected by -0098/-0099/-0104) |\n| Affected versions pinned | `rustls-webpki = \"0.103.1\"` (resolved in `Cargo.lock`) |\n| Maintained version | `rustls-webpki = \"0.103.13\"` (12 patch releases ahead) |\n| Sister crates already updated | `zebra` and `zaino` are on `rustls-webpki = \"0.103.13\"` (patched) |\n| Discovery date | 2026-05-15 |\n\n---\n\n## Summary\n\n`librustzcash`'s `Cargo.lock` and `zallet`'s `Cargo.lock` both resolve `rustls-webpki = \"0.103.1\"` (via `tonic 0.14.2 \u2192 tokio-rustls 0.26.2 \u2192 rustls 0.23.25 \u2192 rustls-webpki 0.103.1`). The TLS path is reachable from `zcash_client_backend/src/tor/grpc.rs:31`:\n\n```rust\n.tls_config(ClientTlsConfig::new().with_webpki_roots())\n```\n\nThis is the wallet's TLS configuration for talking to a `lightwalletd` over Tor. The validating code path is what protects the wallet against a malicious Tor exit node or any other on-path adversary presenting a forged certificate.\n\nThe pinned `0.103.1` is 12 patch releases behind the current `0.103.13`. The intervening releases ship four security advisories:\n\n| Advisory | Date | Class | Patched at | Reachable in `librustzcash`'s usage today? |\n|---|---|---|---|---|\n| [RUSTSEC-2026-0098 (GHSA-965h-392x-2mh5)](https://rustsec.org/advisories/RUSTSEC-2026-0098.html) | 2026-04-14 | URI name-constraint validation gap | \u2265 0.103.12 | **No** \u2014 the advisory itself notes \"this library does not provide an API for asserting URI names\" |\n| [RUSTSEC-2026-0099 (GHSA-xgp8-3hg3-c2mh)](https://rustsec.org/advisories/RUSTSEC-2026-0099.html) | 2026-04-14 | DNS name-constraint bypass when cert asserts a wildcard | \u2265 0.103.12 | **Latent** \u2014 requires a cert chain that uses DNS name constraints (uncommon for typical webpki-roots TLS endpoints, but used in some enterprise PKIs and onion-service cert chains) |\n| [RUSTSEC-2026-0049 (GHSA-pwjx-qhcg-rvj4)](https://rustsec.org/advisories/RUSTSEC-2026-0049.html) | 2026-03-20 | CRL distribution-point matching | \u2265 0.103.10 | **No** \u2014 the librustzcash TLS path does not configure CRL checking |\n| [RUSTSEC-2026-0104 (GHSA-82j2-j2ch-gfr8)](https://rustsec.org/advisories/RUSTSEC-2026-0104.html) | 2026-04-22 | Panic in CRL parsing | \u2265 0.103.13 | **No** \u2014 librustzcash does not parse CRLs |\n\nSo the immediate reachability is limited: only RUSTSEC-2026-0099 has any plausible reach, and even that requires a non-default cert chain shape. `zebra` and `zaino` already sit on `rustls-webpki = 0.103.13`, so the librustzcash + zallet pair are the only ones in the workspace family still on the pre-patch line.\n\nThe reason to disclose this anyway:\n\n1. **Sister crates already moved.** `zebra` and `zaino` updated; `librustzcash` and `zallet` did not. The asymmetry is itself a signal that the move was missed in librustzcash, not a deliberate hold-back.\n2. **Cert-chain shape is operator-controlled.** The reachability assessment \"requires DNS name constraints + wildcard\" depends on what cert is presented to the wallet by the lightwalletd it's configured to talk to. A user pointing the wallet at a corporate-PKI lightwalletd, an onion-service lightwalletd, or any custom CA chain shifts the reachability. A library-level \"this isn't reachable for our default users\" assertion shouldn't bind operators to a default cert profile.\n3. **Tor exit node threat model.** `zcash_client_backend::tor::grpc` is specifically the Tor-routed TLS path. The threat model for a Tor wallet user is \"any exit node may MitM unless TLS validates\" \u2014 TLS validation is the *only* line of defense, and so any latent gap in the validation library is operationally relevant for that user class even when \"not directly exploitable today.\"\n4. **Dep hygiene.** In a workspace with strong dependency-pinning discipline, a 12-patch-version gap on a TLS validation library reads as a missed bump rather than a deliberate constraint. The `Cargo.lock` would update on the next `cargo update -p rustls-webpki`.\n\n## Verification at code level\n\n```\n$ grep -A 2 'name = \"rustls-webpki\"' librustzcash/Cargo.lock\n[[package]]\nname = \"rustls-webpki\"\nversion = \"0.101.7\"          # pulled by an older rustls 0.21\n\u2026\n[[package]]\nname = \"rustls-webpki\"\nversion = \"0.103.1\"          # pulled by current rustls 0.23 (the active TLS path)\n\n$ grep 'rustls 0.23' -A 12 librustzcash/Cargo.lock\n[[package]]\nname = \"rustls\"\nversion = \"0.23.25\"\n\u2026\n \"rustls-webpki 0.103.1\",     \u2190 this is the version active in the tonic path\n\n$ grep -n 'with_webpki_roots' librustzcash/zcash_client_backend/src/tor/grpc.rs\n31:                .tls_config(ClientTlsConfig::new().with_webpki_roots())\n```\n\nFor `zallet`:\n\n```\n$ grep -A 2 'name = \"rustls-webpki\"' zallet/Cargo.lock\n\u2026\nversion = \"0.103.1\"\n```\n\nBoth `zebra` and `zaino` resolve `0.103.13`:\n\n```\n$ grep -A 2 'name = \"rustls-webpki\"' zebra/Cargo.lock zaino/Cargo.lock\nzebra/Cargo.lock:version = \"0.103.13\"\nzaino/Cargo.lock:version = \"0.103.13\"\n```\n\n## Suggested fix\n\nBump `rustls` (and transitively `rustls-webpki`) in `librustzcash` and `zallet` to versions that resolve `rustls-webpki &gt;= 0.103.13`. The `Cargo.toml`s of both repos do not directly pin `rustls-webpki` (it comes through `rustls`), so the actual update is on `rustls` / `tokio-rustls` / `tonic`, or by running `cargo update -p rustls-webpki --precise 0.103.13` and committing the lockfile.\n\n**One additional fix needed**: `librustzcash/supply-chain/config.toml` lists `rustls-webpki 0.103.1` as a `safe-to-deploy` cargo-vet exemption. The version bump must also remove or update that exemption entry, otherwise `cargo vet check` will continue to validate against the old version. This is an easy thing to miss in a routine lockfile-only bump.\n\nA regression test that asserts `cargo audit` on the lockfile passes (or that the `rustls-webpki` resolved version is `&gt;= 0.103.13`) would catch any future drift.\n\n## What I am NOT claiming\n\nI am not claiming any of the four advisories is currently reachable for a default-configuration wallet against a default-configuration lightwalletd. The only one with any plausible reachability is RUSTSEC-2026-0099, and only for cert chains that use DNS name constraints. I am claiming that the dep is out of date, that the asymmetry with the patched sister crates is anomalous, and that the gap is operationally relevant for users whose deployment shape is non-default (corporate PKI, onion-service cert chains, future cert-chain changes).\n\nIf the team's stance is \"yes, we know, we'll bump on the next release,\" that is a sufficient response and I'd document it as such.\n\n---\n\n## Researcher contact\n\nSignal: this thread. Happy to run a follow-up sweep of all `Cargo.lock` files across the librustzcash workspace family for any other dep that's behind the maintained line, if useful.\n", "creation_timestamp": "2026-05-17T18:09:49.000000Z"}]}