blog.rust.gay

PSA: if your program does HTTP requests, update your dependencies

If your Rust program makes HTTP or HTTPS requests, treat dependency updates as a security task, not routine housekeeping. A client, CLI tool, webhook sender, background job, or microservice usually inherits its network behavior from a small stack of crates: reqwest for ergonomic requests, hyper (and often the h2 crate underneath it) for HTTP protocol handling, and rustls for TLS. A flaw in one of those layers can affect applications that never touch the vulnerable code path directly.

In October 2026, RustSec published a cluster of rustls advisories—RUSTSEC-2026-0346, RUSTSEC-2026-0347, RUSTSEC-2026-0348, and RUSTSEC-2026-0349—all patched in rustls 0.23.46. They cover different risks: accidental disclosure of TLS secrets through Debug formatting, buffered application data being sent to the wrong server after Encrypted Client Hello (ECH) rejection, large Brotli certificate decompression allocations when the non-default brotli feature is enabled, and a key-share prediction issue that can downgrade later connections to classical key exchange. None of these is a reason to panic, but together they are a good reminder that HTTP dependencies sit on the security boundary of almost every networked program.

Why HTTP dependency hygiene matters

Any Rust program that communicates with the external world is part of the software supply chain. Even when your own code is small and carefully reviewed, it depends on protocol implementations, parsers, cryptography, compression code, and build-time transitive crates. The practical question is not whether reqwest, hyper, or rustls are "good crates"—they are widely used and actively maintained—but whether your locked dependency graph contains a version with a known issue and whether your process would notice.

The risk is rarely limited to the crate named in an advisory. reqwest commonly pulls in HTTP and TLS behavior from lower layers. hyper uses protocol crates such as h2 for HTTP/2. rustls can be selected directly or indirectly, and optional features can change its exposure. That means a security review based only on direct dependencies in Cargo.toml is incomplete. Cargo.lock is the artifact that records what your build actually resolved.

The layered architecture of risk

The security posture of a Rust HTTP application is distributed across a layered stack. At the base are protocol implementations such as hyper and, for HTTP/2, h2. Above that sits TLS, often provided by rustls, which performs the handshake and protects traffic in transit. Higher-level clients such as reqwest provide an ergonomic API for requests, redirects, bodies, and JSON handling.

This composition creates cascading exposure. A low-level protocol or TLS issue can affect every application that depends on the stack, even if the application only calls a simple client.get(url).send().await. The impact still depends on configuration: an issue may require a specific feature, a specific protocol mode, client certificates, ECH, custom session storage, or a particular server behavior. Accurate triage means matching the advisory's affected versions and conditions to the versions and features in your lockfile.

RUSTSEC-2026-0346 is a useful example of how operational context matters. RustSec reported that derived Debug implementations for Tls12ClientSessionValue and Tls13ClientSessionValue included the TLS 1.2 master secret or TLS 1.3 resumption pre-shared key (PSK). Ordinary connection and built-in session cache Debug output were redacted and not affected, but a custom ClientSessionStore that formatted these values with Debug—for example in logs—could disclose those secrets. The fix is to upgrade to rustls 0.23.46 or later; the defensive lesson is broader: sensitive key material must never be treated as harmless diagnostic output.

What the October 2026 rustls advisories actually say

The October 14, 2026 RustSec publications for rustls are worth reading carefully because the affected configurations differ. Treating them as one generic "TLS bug" makes triage harder than it needs to be.

RUSTSEC-2026-0346 reports that Debug implementations for Tls12ClientSessionValue and Tls13ClientSessionValue were derived and therefore included the TLS 1.2 master secret or TLS 1.3 resumption PSK. RustSec classifies it as a vulnerability in the crypto-failure category (CVSS 4.0, local attack vector, high confidentiality impact in the affected scenario). Custom ClientSessionStore implementations that format these values with Debug could disclose secrets to log readers. Ordinary connection and built-in session cache Debug output were redacted and not affected. The issue is patched in rustls 0.23.46; versions before 0.21.0 are listed as unaffected.

RUSTSEC-2026-0347 covers buffered application data sent to the outer server after ECH rejection. When a server rejects Encrypted Client Hello, the client completes the handshake using the outer ClientHello and authenticates the server against the ECH config's public_name. RustSec reports that rustls encrypted and queued application data buffered via ClientConnection::writer() before queueing the ech_required alert. The application receives Error::PeerIncompatible(ServerRejectedEncryptedClientHello), but the next write_tls() call can also send the buffered application data, so plaintext meant for the inner server can go to the authenticated outer server named by public_name. This affects only clients that use EchMode::Enable (GREASE ECH is not affected), write application data before the handshake completes, and connect to a server that rejects their ECH config. It is patched in 0.23.46 and listed as unaffected before 0.23.44.

RUSTSEC-2026-0348 is a denial-of-service risk in the non-default brotli certificate decompression feature. The built-in Brotli certificate decompressor bounded decompressed output but accepted a peer-selected history window; Brotli's large-window extension was enabled, so a five-byte header could cause a 1 GiB allocation before authentication. RustSec notes that no trusted certificate is required. Affected configurations are clients that enable the brotli feature and connect to a malicious server, and servers that enable the brotli feature and request client certificates. Applications that do not enable brotli are not affected. The fix is in 0.23.46; versions before 0.23.9 are listed as unaffected.

RUSTSEC-2026-0349 (also tracked as GHSA-w44r-93j7-qv28) reports that the client recorded the server-selected key exchange group for each server name when processing the ServerHello, before the server was authenticated. That record is used to predict which key share to send in future ClientHellos. A network attacker could answer one connection attempt with a ServerHello selecting X25519 and then close the connection; later ClientHellos for that name could carry only an X25519 key share. If the genuine server accepts that share rather than sending a HelloRetryRequest for its preferred hybrid group, the handshake can complete with classical-only key exchange. RustSec describes the impact as loss of post-quantum confidentiality for affected connections ("harvest now, decrypt later"), with no key compromise and no authentication bypass. It is patched in 0.23.46.

These details matter because they change the response. A team with a custom rustls session store should review logging and secret handling immediately. A team that does not use ECH or the brotli feature may still need to upgrade because the same release line is affected, but the urgent mitigations differ. A team that only uses reqwest defaults should still check the resolved rustls version, because transitive upgrades and feature unification can pull in affected code.

Protocol layers below your HTTP client

HTTP security is not only a TLS story. The h2 crate, used by hyper for HTTP/2, had its own issue tracked as RUSTSEC-2026-0258: it could accept and queue empty DATA frames without limit. RustSec reported that if streams were not actively drained, this could lead to unbounded memory usage or a panic if the length overflowed. The advisory marks the issue as low severity and patched in h2 0.4.16. The practical lesson is that "update HTTP dependencies" means the full protocol stack, not only the crate whose name appears in your application code.

This is why component selection should be security-aware. reqwest is a good default for application-level HTTP requests. hyper is the right place to look when you need lower-level control over HTTP behavior or when you are operating servers and proxies. rustls deserves explicit attention because it sits on the cryptographic boundary and because its feature flags and operational integrations (session stores, ECH, certificate compression) change the attack surface. When you read an advisory, check both the crate version and the configuration that makes the issue reachable.

Transitive dependencies and the lockfile

The most common failure mode in dependency response is auditing only direct dependencies. A project may depend on reqwest, which depends on HTTP and TLS crates, which may depend on protocol, parsing, or compression crates. The vulnerable version can be several edges away from Cargo.toml. That is normal for Cargo ecosystems; the mistake is treating it as invisible.

Cargo.lock is the source of truth for what was resolved. Use it to answer concrete questions: Which version of rustls is present? Is there more than one version? Which crates depend on it? Are optional features enabled through feature unification? The commands are simple:

cargo tree -i rustls
cargo tree -i h2
cargo audit

Transitive risk is not limited to HTTP crates. For example, RustSec advisories RUSTSEC-2021-0157 and RUSTSEC-2026-0373 affected ammonia in specific configurations involving CSS sanitization and URL handling. RUSTSEC-2021-0157 described incorrect escaping for url("") functions in CSS style attributes when the style attribute is allowed and filter_style_properties is used. RUSTSEC-2026-0373 described missing case-insensitive matching for the CSS url function when CSS sanitization is used, which could allow URL filtering to be bypassed in that configuration. These are not HTTP client bugs, but they illustrate the same supply-chain pattern: the risky behavior depends on versions, features, and how a dependency is used. The response should be precise, not generic.

A practical update workflow

Reactive patching after downstream packaging updates or social media posts is better than doing nothing, but it leaves too much to chance. A safer workflow is boring and repeatable:

  1. Identify. Run cargo audit in CI and locally. It checks the resolved dependency graph against the RustSec database, including transitive dependencies.
  2. Inspect. When an advisory appears, use cargo tree -i <crate> to find every path that pulls the crate in. Confirm the exact version in Cargo.lock and note whether multiple versions are present.
  3. Plan. Use cargo update -p <crate> for a targeted fix when appropriate, or a broader update when the dependency graph requires it. Read release notes when crossing minor versions or changing TLS/HTTP behavior.
  4. Verify. Run tests, integration tests, and any smoke tests that exercise real network behavior. For HTTP stacks, check TLS configuration, proxies, client certificates, HTTP/2, and any custom session or logging code.
  5. Prevent regressions. Add cargo deny policies where useful, keep dependencies reasonably current, and avoid custom code that logs sensitive internal types with Debug unless you have verified the output is redacted.

cargo deny is not a replacement for cargo audit. audit tells you about known advisories; deny helps enforce policy, such as blocking certain crates, versions, licenses, or sources. Used together, they make dependency security visible before release.

Mitigations that are easy to miss

Some fixes are version bumps; others require a small operational change. The RustSec cluster around rustls is a good example.

For RUSTSEC-2026-0346, upgrading to rustls 0.23.46 is the direct fix. If you maintain a custom ClientSessionStore, also review any code that formats session values with Debug or logs them. Redaction should happen at the boundary where secrets are handled, not as an assumption about library internals.

For RUSTSEC-2026-0348, check whether the rustls brotli feature is enabled. If you do not need Brotli certificate compression, leaving the feature disabled reduces exposure to that code path. If you do need it, upgrade to a patched version.

For RUSTSEC-2026-0347, ECH users should update and review code that writes application data before the handshake completes. The issue has narrow conditions, but it is exactly the kind of protocol edge case that is hard to notice during ordinary application testing.

For RUSTSEC-2026-0349, update rustls and understand that the reported impact is loss of post-quantum confidentiality for affected connections under specific conditions. It is not described as key compromise or authentication bypass, so the right response is measured: patch, evaluate exposure, and avoid overstating the bug.

Conclusion

The useful version of "update your HTTP dependencies" is specific. Know which crates implement HTTP, HTTP/2, and TLS in your build. Read the advisory conditions, not just the crate name. Check Cargo.lock, not just Cargo.toml. Upgrade to patched versions, then verify that your tests and operational assumptions still hold.

For Rust programs that make HTTP requests, dependency hygiene is part of the security boundary. cargo audit, cargo tree, and a disciplined update process will not prevent every supply-chain problem, but they make known issues visible early—when the fix is usually a controlled update instead of an emergency.

Sources


← back to posts