Skip to content

fix(tls): trust the OS certificate store on relay WebSocket connections - #3455

Open
benjaminkitt wants to merge 1 commit into
block:mainfrom
benjaminkitt:native-roots
Open

fix(tls): trust the OS certificate store on relay WebSocket connections#3455
benjaminkitt wants to merge 1 commit into
block:mainfrom
benjaminkitt:native-roots

Conversation

@benjaminkitt

Copy link
Copy Markdown

Problem

The desktop app's relay WebSocket trusts only a compiled-in Mozilla root list and ignores the OS trust store, so Buzz cannot connect to a relay from a machine behind a TLS-inspecting proxy (Netskope, Zscaler, Palo Alto, …). The connection fails with:

invalid peer certificate: UnknownIssuer

There is no user- or admin-side workaround: installing the corporate CA into the system keychain has no effect, and neither SSL_CERT_FILE nor NODE_EXTRA_CA_CERTS is consulted on this path.

This is easy to misdiagnose, because everything else in the app works. HTTPS requests go through reqwest, which uses rustls-platform-verifier and does read the OS trust store. Only the WebSocket path is affected, so the app looks healthy right up until the relay never connects.

Root cause

  1. desktop/src-tauri/Cargo.toml and the root Cargo.toml build tokio-tungstenite with only the rustls-tls-webpki-roots feature. rustls-tls-native-roots is not enabled anywhere in the workspace, so feature unification does not compensate.
  2. desktop/src-tauri/src/native_websocket.rs calls bare connect_async(url) with no custom Connector, so tokio-tungstenite builds its default root store — which under that feature set is the bundled Mozilla roots only.
  3. desktop/src/shared/api/relayClientSession.ts and readOnlyRelayClient.ts reach the relay via invoke("plugin:websocket|connect"), so relay traffic goes through the Rust path above rather than the webview. (The webview would have used the OS trust store and would not have this problem.)

The same applies to the workspace dependency, so buzz, buzz-acp and buzz-dev-mcp are affected too. buzz-agent uses reqwest only and is unaffected.

Confirmed against a v0.5.0 release build: the binary contains the Mozilla root names (ISRG Root X1, DigiCert Global Root G2, …) and contains SecTrustEvaluateWithError (rustls-platform-verifier, via reqwest) but no rustls_native_certs / load_native_certs symbols.

Fix

Enable rustls-tls-native-roots alongside the existing feature, in both manifests.

The two root-store features are additive — tokio-tungstenite's src/tls.rs pushes both sets into the same RootCertStore, and the webpki branch is not gated on not(rustls-tls-native-roots). So this preserves today's behaviour for everyone (the bundled Mozilla roots are still there) and additionally honours the platform trust store.

Verification

Reproduced with a standalone tokio-tungstenite 0.29 probe against a real proxied relay, connecting with each root store in turn:

(a) webpki-roots only      => FAILED: invalid peer certificate: UnknownIssuer
    native cert load: added=148 ignored=0 errors=[]
(b) native + webpki roots  => SUCCESS (TLS + WS handshake completed)

I then built the full app from this branch and confirmed the relay connects normally on the affected machine, and that the rebuilt buzz / buzz-acp / buzz-dev-mcp sidecars pick up the change through the workspace dependency.

Alternative worth considering

Since the app already depends on rustls-platform-verifier through reqwest, an arguably tidier fix is to build a Connector::Rustls from the platform verifier and pass it to connect_async_tls_with_config. That would make WebSocket trust behaviour match HTTP trust behaviour instead of having the two paths use different trust stores. Happy to rework this PR that way if you'd prefer it.

@benjaminkitt
benjaminkitt requested a review from a team as a code owner July 29, 2026 02:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d285851cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Cargo.toml

# WebSocket client (test client)
tokio-tungstenite = { version = "0.29", features = ["rustls-tls-webpki-roots"] }
tokio-tungstenite = { version = "0.29", features = ["rustls-tls-webpki-roots", "rustls-tls-native-roots"] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sign off this commit for DCO

The reviewed commit has no Signed-off-by trailer, so the repository's required DCO Check will reject it before these manifest changes can merge; recreate or rebase the commit with --signoff.

AGENTS.md reference: AGENTS.md:L111-L111

Useful? React with 👍 / 👎.

tokio-tungstenite is built with only rustls-tls-webpki-roots, and native_websocket.rs calls bare connect_async() with no custom Connector, so the relay WebSocket trusts a compiled-in Mozilla root list and nothing else. Behind a TLS-inspecting proxy the re-signed certificate is rejected with 'invalid peer certificate: UnknownIssuer', and no keychain entry, SSL_CERT_FILE or NODE_EXTRA_CA_CERTS can influence it.

This is easy to misdiagnose because HTTPS works: reqwest uses rustls-platform-verifier, which does read the OS trust store. Only the WebSocket path is affected, so the app looks healthy until the relay never connects.

The two root-store features are additive (tls.rs pushes both into one RootCertStore, and the webpki branch is not gated on not(rustls-tls-native-roots)), so this preserves current behaviour and additionally honours the platform store.

Signed-off-by: Benjamin Kitt <ben@thepuddle.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant