{"uuid": "05da3d34-46e1-41d6-a67e-10393b9ac59a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-xgp8-3hg3-c2mh", "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"}