Skip to content

fix(deps): update rust crate russh to 0.63.0 [security] - autoclosed - #346

Closed
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/crate-russh-vulnerability
Closed

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/crate-russh-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Type Update Change
russh dependencies minor 0.62.7 → 0.63.0

Russh: Missing X25519 zero-point validation in hybrid ML-KEM key exchange

CVE-2026-102824 / GHSA-w3jg-pjxf-73p4

More information

Details

Vulnerability

The hybrid ML-KEM 768 + X25519 key exchange implementation in russh/src/kex/hybrid_mlkem.rs does not validate that the remote peer's X25519 public key is not the zero point (all-zero 32-byte value). This allows a remote peer to force the X25519 contribution to the combined shared secret to zero, reducing the hybrid KEX to a single-algorithm exchange.

Affected code at HEAD (v0.62.4, commit 0089c89):

Server-side (server_dh, lines 92-93):

let mut c_pk1 = MontgomeryPoint([0; 32]);
c_pk1.0.copy_from_slice(c_pk1_bytes);
// No zero-point check - proceeds to: let k_cl = s_secret * c_pk1;

Client-side (compute_shared_secret, lines 154-155):

let mut s_pk1 = MontgomeryPoint([0; 32]);
s_pk1.0.copy_from_slice(s_pk1_bytes);
// No zero-point check - proceeds to: let k_cl = x25519_secret * s_pk1;
Root Cause

Commit a7fc1eb (2026-07-22, "fix mpint encoding and validate curve25519 keys") added zero-point validation to the standalone Curve25519 KEX in russh/src/kex/curve25519.rs at two locations:

  • server_dh line 77: if client_pubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); }
  • compute_shared_secret line 122: if remote_pubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); }

The same X25519 scalar multiplication pattern appears in hybrid_mlkem.rs, but the fix was not applied there.

Proof of Concept

A malicious SSH client negotiating mlkem768x25519-sha256 can send a KEX_HYBRID_INIT message containing a valid ML-KEM 768 encapsulation key followed by 32 zero bytes as the X25519 component.

When the server computes k_cl = s_secret * c_pk1, the result is the zero point regardless of the server's secret scalar. The combined shared secret K = SHA-256(k_pq || k_cl) then depends only on the ML-KEM component. The same attack works in reverse against a client connecting to a malicious server.

The zero X25519 public key passes all existing validation (the length check on line 78 succeeds since 32 bytes is correct). No panic or crash occurs - the exchange completes successfully with a weakened shared secret.

Impact

The purpose of hybrid key exchange is defense-in-depth: if either the classical algorithm (X25519) or the post-quantum algorithm (ML-KEM 768) is broken, the combined secret remains secure. By injecting a zero X25519 public key, an attacker eliminates the classical contribution entirely, reducing security to depend solely on ML-KEM.

This matters in two scenarios:

  1. If ML-KEM 768 is later found to have a weakness (the explicit threat model hybrid KEX is designed to mitigate), sessions where the X25519 component was zeroed out lose their fallback protection.
  2. An active network attacker who can intercept KEX could downgrade the hybrid exchange to effectively single-algorithm security without either peer detecting it.

The severity is MEDIUM rather than HIGH because the ML-KEM component still provides strong security today, and an active attacker who can modify KEX messages can already perform other attacks unless strict KEX is negotiated.

Suggested Fix

Add zero-point checks in hybrid_mlkem.rs matching the ones in curve25519.rs:

// In server_dh, after line 93:
let mut c_pk1 = MontgomeryPoint([0; 32]);
c_pk1.0.copy_from_slice(c_pk1_bytes);
if c_pk1.0 == [0u8; 32] {
    return Err(Error::Kex);
}

// In compute_shared_secret, after line 155:
let mut s_pk1 = MontgomeryPoint([0; 32]);
s_pk1.0.copy_from_slice(s_pk1_bytes);
if s_pk1.0 == [0u8; 32] {
    return Err(Error::Kex);
}

Ideally, also validate against the other small-subgroup points on Curve25519 (there are a small number of low-order points that also yield a zero shared secret), matching the comprehensive validation OpenSSH performs.

AI tooling

AI assistancewas used for the code audit and for drafting this report. The finding was manually verified against the project's source at the location cited above before reporting it, and the severity and impact assessment are my own.

Severity

  • CVSS Score: 4.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


russh: Client-side channel-scoped Handler callbacks fire for channel IDs the client never opened

CVE-2026-102823 / GHSA-47hw-gvq5-r2gm

More information

Details

Summary

CVE-2026-68930 was fixed by adding Session::is_established_channel() in russh/src/server/encrypted.rs, which gates every channel-scoped SERVER-side message (CHANNEL_REQUEST, CHANNEL_DATA, CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_WINDOW_ADJUST, CHANNEL_EXTENDED_DATA) on enc.channels.get(&channel).is_some_and(|c| c.confirmed) before invoking any Handler callback. The identical validation was never added to the CLIENT side (russh/src/client/encrypted.rs), which processes channel-scoped messages sent by the SSH SERVER once the client has authenticated.

Details

In client_read_authenticated (russh/src/client/encrypted.rs, ~lines 431-757), for CHANNEL_DATA, CHANNEL_EXTENDED_DATA, CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_OPEN_FAILURE, CHANNEL_SUCCESS, CHANNEL_FAILURE, and the CHANNEL_REQUEST sub-types exit-status/exit-signal/xon-xoff, the code only optionally forwards the event to the internal per-channel mpsc sender via if let Some(chan) = self.channels.get(&channel_num) { ... } (a no-op if the channel is unknown), but then unconditionally calls the corresponding public Handler trait method (client.data(...), client.exit_status(...), client.channel_close(...), client.channel_success(...), etc.) regardless of whether channel_num corresponds to any channel the client ever opened or that was ever confirmed. Only CHANNEL_OPEN_CONFIRMATION (closes the connection with Error::Inconsistent if unknown) and CHANNEL_WINDOW_ADJUST (returns early with Ok(()) if unknown) correctly validate channel existence before acting.

Corroborating evidence this check was intended but never wired up: crate::Error defines a dedicated WrongChannel variant documented as "Message received/sent on unopened channel" (russh/src/lib_inner.rs, ~line 144-146), yet a repo-wide search shows this variant is never constructed or returned anywhere in the codebase — dead code left over from (or intended for) exactly this validation.

Because Session::new_channel_id() (russh/src/session.rs, ~line 708) allocates channel IDs sequentially starting at 1, a malicious or compromised SSH server can trivially predict the ID of the client's next channel and inject spoofed lifecycle events for it before or interleaved with the real channel-open exchange, or replay events for already-closed channel IDs.

PoC

Many real-world consumers of russh-as-a-client (deployment/orchestration tools, CI runners connecting to build/bastion hosts, git-over-ssh style tooling, database/tunnel clients) implement the client::Handler trait directly and key their own state (e.g. HashMap<ChannelId, CommandState>, exit-code trackers, per-channel byte counters, completion futures) off the channel IDs the library hands them, trusting the documented contract that events like "The remote process has exited" (exit_status) or "Called when the server closes a channel" (channel_close) only fire for a channel the application itself opened.

A malicious, MITM'd (via a compromised/rogue jump host the client is configured to trust), or simply hostile SSH server can send SSH_MSG_CHANNEL_REQUEST (exit-status/exit-signal), SSH_MSG_CHANNEL_DATA, SSH_MSG_CHANNEL_CLOSE, SSH_MSG_CHANNEL_SUCCESS/FAILURE, or SSH_MSG_CHANNEL_OPEN_FAILURE for an arbitrary/predicted/never-opened channel ID at any point after authentication completes. Because the library invokes the Handler callback unconditionally, this reaches application code with an ID it never registered.

Impact

(1) A reliable, purely protocol-level trigger for an application panic/DoS in any client that indexes per-channel state by ChannelId without itself re-checking channel validity — the exact class of bug CVE-2026-68930 fixed server-side; and (2) lets the server spoof exit-status/exit-signal/close/success/failure notifications for a channel the client has not yet opened or has already released, desynchronizing the client's command-completion bookkeeping (e.g. reporting a forged exit code 0 for a not-yet-run remote command, or a premature channel_close before real output/exit-status has arrived) — a business-logic-level integrity violation of the SSH channel lifecycle that automation built on russh implicitly relies on.

Suggested fix: add the same is_established_channel()-style gate already used in server/encrypted.rs to client/encrypted.rs's client_read_authenticated, checking self.channels.get(&channel_num) before invoking any Handler callback (not just the mpsc forward), for every channel-scoped message type.

For credit/changelog purposes, please use: Yazan Balawneh, Cystack.ps

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic

CVE-2026-102822 / GHSA-p8qx-h547-fjw9

More information

Details

Summary

SshBlockCipher implementations (AES-CBC, AES-CTR, 3DES-CBC, etc.) report needs_mac() == true, meaning they are documented/intended to always be paired with a separate integrity MAC. However, key-exchange negotiation only checks needs_mac() inside the fallback branch of MAC algorithm selection (used when no common MAC algorithm exists). If both peers' preferred MAC lists simply contain none and it is successfully negotiated through the normal selection path, nothing rejects pairing none with a cipher that requires a MAC. Once negotiated, a single crafted packet from either peer causes cipher::read() to shrink an already-allocated buffer below the number of bytes it is about to index, causing a Rust slice-index-out-of-range panic and killing that connection's task.

Details

In russh/src/cipher/mod.rs, read() for a block cipher:

  1. Reads packet_length_to_read_for_block_length() bytes up front (16 bytes for any SshBlockCipher) into buffer.buffer.
  2. Decrypts the first block to recover the plaintext packet-length field len.
  3. Computes buffer.len = len + cipher.tag_len().
  4. Calls buffer.buffer.resize(buffer.len + 4, 0).
  5. Immediately indexes buffer.buffer[16..] (via the constant used for the first block read) to continue decrypting/reading the rest of the packet.

When the negotiated MAC is none, tag_len() == 0. If the attacker (or a MITM holding the session key, or simply the accepting peer testing a hostile client) sends a packet whose decrypted length field is 0, then buffer.len = 0 and resize(0 + 4, 0) shrinks the buffer that was already grown to 16 bytes in step 1 down to 4 bytes. The subsequent slice operation buffer.buffer[16..] then panics with range start index 16 out of range for slice of length 4.

The file already defines a MINIMUM_PACKET_LEN constant, but it is only consulted on the write/padding side, never on the read path — so nothing prevents an incoming packet from declaring a length shorter than the bytes already buffered.

Negotiation gap: negotiation.rs's Select MAC-selection logic only special-cases needs_mac() when negotiation would otherwise fail (no common MAC), substituting none only if the cipher does not need one. It never re-validates the case where none is a common/successfully-negotiated MAC on both sides regardless of what the chosen cipher requires. So an application (or a malicious peer, since negotiation is attacker-influenced on one side) that includes none in its own preferred MAC list — while still allowing the default CTR/CBC cipher suite — ends up with an invalid, panic-inducing combination that the library itself should refuse.

PoC
  1. Configure one side's Preferred config to include mac::NONE in the MAC list (this is a supported, non-default configuration exposed by the crate's public Preferred API — used e.g. for legacy/interop compatibility), while leaving the default cipher list (which includes aes256-ctr/aes256-cbc) untouched.
  2. Complete a normal key exchange; negotiation lands on {cipher: aes*-ctr (or -cbc), mac: none} because none is present and preferred/common on both sides, and nothing during negotiation rejects this pairing.
  3. From the peer, send one transport packet whose decrypted packet-length field is 0 (trivial to construct once the session keys are known to that peer, or for the peer that legitimately owns the connection to simply hand-craft, e.g. a modified client for testing).
  4. cipher::read() on the receiving side panics: range start index 16 out of range for slice of length 4.
  5. Because each connection is handled in its own tokio::spawn'ed task (see the per-connection select! loop that calls into cipher::read()), the panic unwinds only that task by default, but it unconditionally terminates that SSH connection/session — a working, currently-unauthenticated, already-established connection is killed with no attacker interaction beyond the one crafted packet, and the check runs on every inbound packet including pre-auth ones.
Impact

Denial of service: a remote peer that can influence MAC preference negotiation (or a MITM in possession of the session key) can crash any individual SSH connection/session that ends up negotiating a block cipher together with mac=none, with a single crafted packet, pre-authentication. This does not affect the process as a whole (panic is scoped to that connection's task under panic=unwind), and does require a non-default configuration that permits none as a preferred MAC — hence Low severity.

Suggested fix

In the MAC-selection logic in negotiation.rs, reject (or force substitution of) mac::NONE whenever the negotiated cipher's needs_mac() is true, regardless of how none came to be selected — not only in the "no common MAC" fallback branch. Defensively, cipher::read() could also refuse to shrink a buffer below the number of bytes already consumed for the length-field block, returning a protocol error instead of resizing blindly.

For credit/changelog purposes, please use: Yazan Balawneh, Cystack.ps

Severity

  • CVSS Score: 3.7 / 10 (Low)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Russh: Unbounded memory exhaustion via CHANNEL_OPEN flood during a client-stalled rekey

CVE-2026-102821 / GHSA-35g8-35p8-c8fw

More information

Details

Summary

A russh server can be driven to unbounded heap growth (process OOM / kill) by
a peer that speaks only standard SSH messages, in the default configuration.

The peer starts a key re-exchange (sends SSH_MSG_KEXINIT) but never sends the
follow-up SSH_MSG_KEX_ECDH_INIT, leaving the server's kex state machine in
SessionKexState::InProgress indefinitely. While a rekey is in progress the
server's three message-drain paths are all gated off (if !self.kex.active()),
but the network-read path stays active, so every SSH_MSG_CHANNEL_OPEN the peer
sends is processed inline and appends one reply to an unbounded internal
queue (priority_receiver, an UnboundedReceiver) that is not dequeued until
the rekey completes. Because the peer decides whether the rekey ever completes,
the queue — and the server's memory — grows without bound.

This is reproducible end-to-end against a real russh server over a real
encrypted transport; see "Proof of concept". A one-line negative control (same
flood, no rekey) keeps memory flat, isolating the rekey window as the sole
trigger.

Impact
  • Availability / DoS. Server RSS climbs at roughly 3.3 KB per
    CHANNEL_OPEN, driven entirely by the peer, until the process is OOM-killed.
    In the reproduction one connection pushed the server from ~4 MB to 2.57 GB
    (and past 4.8 GB against the stock echoserver example) and it was still
    climbing when the flood was stopped.
  • Reached with standard messages, no special configuration. Any handler is
    affected, including one that rejects every channel (the reply enqueued on
    rejection is exactly what accumulates). There is no per-connection cap on
    in-flight channel opens or on the queue, and the queue's sender has no
    backpressure.
  • The peer keeps the connection alive simply by continuing to send, so the
    inactivity timer never fires.
Affected component
  • russh/src/server/session.rs — Session::run tokio::select! loop. The
    network-read arm is ungated; the three drain paths are gated on
    !self.kex.active().
  • russh/src/server/mod.rs — reply() routes a non-kex message received during
    a rekey straight to server_read_encrypted (inline processing).
  • russh/src/lib_inner.rs — ChannelOpenHandleInner's accept/reject/Drop
    all send a reply on an UnboundedSender.

Verified on v0.63.1 (commit d3ae702), which is the latest release. The
gating logic predates it.

Details

Session::run (russh/src/server/session.rs:631) drives a tokio::select!
(:713). Three of its message-drain sites are gated on !self.kex.active():

  • the pre-select! batch drain of priority_receiver/receiver
    (session.rs:680),
  • the priority_receiver.recv() arm (session.rs:762),
  • the receiver.recv() arm (session.rs:770, which also holds the only other
    priority_receiver drain at :777).

The fourth arm, r = &mut reading (session.rs:714), is ungated: it reads
and processes one incoming packet every loop iteration regardless of rekey
state, calling reply() (server/mod.rs:1128).

During a rekey, session.common.encrypted.is_some(), so the strict-kex
message-ordering guard (server/mod.rs:1143, which is additionally gated on
encrypted.is_none()) does not apply. A non-kex message therefore falls
through reply() to session.server_read_encrypted(handler, pkt)
(server/mod.rs:1232) and is handled inline. For SSH_MSG_CHANNEL_OPEN this
reaches the channel-open handling, which hands the application a
ChannelOpenHandle.

Whether the handler accepts or (the trait default) rejects, the handle's
accept / reject / Drop all send a Msg::ChannelOpenReply on an
UnboundedSender (russh/src/lib_inner.rs:560-603; Drop sends
AdministrativelyProhibited at :594-603). That sender feeds
priority_receiver, declared UnboundedReceiver<Msg> (session.rs:23) and
created with tokio::sync::mpsc::unbounded_channel() (session.rs:1522). Its
only drain sites are the three arms gated off during the rekey. So each
CHANNEL_OPEN processed during the rekey window appends one reply (carrying a
PendingChannelOpen = channel params + mpsc ChannelRef + ids, a few KB
retained in practice) to a queue that is never dequeued.

Two facts make this unbounded and remote:

  1. The server enters InProgress the moment it receives the peer's KEXINIT
    (server/mod.rs:1153-1158, begin_rekey) and only leaves it upon receiving
    the peer's KEX_ECDH_INIT. The peer decides whether to ever send that,
    so the window is attacker-held.
  2. There is no per-connection cap on channels or on the priority queue, and the
    sender is unbounded (no backpressure).
Root cause

The intended design was to buffer packets received during a rekey and replay
them afterwards: the fields pending_reads: Vec<Vec<u8>> and pending_len: u32
(session.rs:26-27) exist and are drained at kex completion
(server/mod.rs:1194-1198). But nothing ever pushes to pending_reads or
increments pending_len (they are dead — confirmed by grep across
russh/src/). Instead of being buffered, channel messages received during a
rekey are processed inline, and their replies pile up in the unbounded
priority_receiver. The missing piece is a bound on — or bounded deferral of —
channel processing while kex.active().

Proof of concept

Everything runs inside a container; nothing touches the host.

Lab. poc/Dockerfile builds russh-lab:head from Eugeny/russh @​ d3ae702
(v0.63.1), default features (rust 1.91). A raw-SSH-client PoC
(poc/poc_rekey_dos.rs) implements curve25519-sha256 / ssh-ed25519 /
aes256-ctr / hmac-sha2-256 by hand, completes the handshake and a publickey
auth, then:

  1. sends SSH_MSG_KEXINIT (server enters kex.active()),
  2. never sends KEX_ECDH_INIT (rekey stalls, attacker-held),
  3. floods SSH_MSG_CHANNEL_OPEN.

A minimal server (poc/poc_server.rs) uses the trait-default
channel_open_session (which rejects by dropping the handle), so the measured
growth is purely the undrained priority queue, not accepted-channel state.

poc/run.sh runs the attack leg and an identical negative control with no
rekey. Fresh run inside the lab, N = 800,000 opens
(results/rerun-2026-08-29.log):

=== ATTACK (rekey stall) === (poc_server baseline_RSS=4208KB, N=800000)
  t=2s  server_RSS=667760KB
  t=4s  server_RSS=1599600KB
  t=6s  server_RSS=2556016KB
  t=8s  server_RSS=2566256KB   (flood done; memory retained)
=== CONTROL (no rekey) === (poc_server baseline_RSS=4208KB, N=800000)
  t=2s..t=12s  server_RSS=4208KB   (flat throughout)
  • ATTACK: RSS 4,208 KB → 2,566,256 KB (~2.57 GB) and retained after the
    flood ends — ~3.3 KB per CHANNEL_OPEN, attacker-driven. (An earlier canonical
    run reached 2.7 GB at 800k opens, and past 4.8 GB against the stock
    echoserver example at 1.5 M opens — see results/canonical-run.log.)
  • CONTROL: RSS flat at 4,208 KB. Without the rekey window the replies are
    drained normally; TCP backpressure (the client never reads the failure
    replies) even throttles the flood.

The control isolates the rekey window as the sole trigger. Both legs exercise
the real server entry point (Session::run → reply → server_read_encrypted)
over a real encrypted transport.

Reproduce: C=russh-lab N=800000 ./poc/run.sh (see poc/POC-README.md).

Remediation

The priority queue carries locally generated channel-open replies, which are
non-kex messages the server must not send during a rekey anyway (RFC 4253
§7.1). So the fix is to bound how much channel work is done during a rekey, not
to drain the queue mid-rekey. Any of:

  • (recommended, minimal) cap the number of non-kex messages processed while a
    rekey is in progress and disconnect a peer that exceeds it. A stalled/abusive
    rekey is then torn down after a small constant instead of growing memory
    without bound. See patch/rekey-message-cap.patch (a ~10-line change local to
    Session::run, validated in the lab — the attack leg is disconnected after the
    cap and RSS stays flat; a normal rekey, which completes in one round trip, is
    unaffected).
  • bound / deferred-buffer channel messages during a rekey using the existing
    (currently dead) pending_reads/pending_len machinery, with a hard cap.
  • bound the number of in-flight channel opens per connection and reject beyond it.

OpenSSH does not service new channels mid-kex; matching that intent closes the
whole family.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

warp-tech/russh (russh)

v0.63.2

Compare Source

Security fixes

GHSA-g4mp-vgx3-xrvm - out-of-bounds read in pageant

A malicious Pageant agent could cause an out-of-bounds read / oversized allocation in the pageant library user.

GHSA-35g8-35p8-c8fw - unbounded memory allocation in server

An authenticated client could trigger unbounded memory allocation during rekey phase

Fixes

  • client: encode the negotiated hash algorithm for RSA certificates (#​764) #​764 (Jeongkyu Shin)
  • 4206815: Fix pty-req terminal modes: deliver them unpadded, encode the right l… (#​755) (tluyben) #​755
  • b1d3893: fixed #​762 - redact sensitive data from debug logging (Eugene)
  • 66789f4: fixed #​761 - data write split across a kex breaks (Eugene)
  • a04e1b5: fixed #​758 - fail RSA signing explicitly when RSA feature is not enabled (Eugene)
  • 422123c: dedup zlib compress loop into compress_into (Eugene)

v0.63.1

Compare Source

Security fixes

GHSA-47hw-gvq5-r2gm - client-side Handler callbacks reachable with invalid channel IDs

A mirror of GHSA-m65r-rprj-r5rg for the client side - Handler per-channel callbacks are called even when the server supplies an invalid (never opened) channel ID. Depending on what the handler does this can lead to a vulnerability.

GHSA-p8qx-h547-fjw9 - MAC-requiring block cipher can be negotiated without MAC and panic

Two peers disagreeing on supported MACs can end up negotiating none MAC for a cipher that requires one, which leads to the session task panicking.

v0.63.0

Compare Source

Features

  • 09f6582: Support host certificates on the client side (#​752) (@​biao29) #​752

    • This changes the signature of Handler::check_server_key to take a new PublicKeyOrCertificate enum instead of &PublicKey
  • d7601ae: Support host certificates on the server side (#​641) (Georg von Zengen) #​641

    • Adds a Config::certificates that functions similarly to Config::keys

Fixes

  • f2354c7: improve strict kex checks (Eugene)
  • 0363fde: fixed PKCS#8 parsing panicking on incorrect contents (Eugene)
  • 46c927a: use constant-time comparison for agent unlock (Eugene)
  • 8da8967: sanitize Curve25519 params (Eugene)

Full Changelog: Eugeny/russh@v0.62.7...v0.63.0


Configuration

📅 Schedule: (in timezone Europe/Budapest)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot force-pushed the renovate/crate-russh-vulnerability branch from 4f114d0 to 30ce244 Compare October 1, 2026 08:34
@renovate
renovate Bot force-pushed the renovate/crate-russh-vulnerability branch from 30ce244 to 1374b4d Compare October 1, 2026 09:29
vdavid added a commit that referenced this pull request Oct 1, 2026
…osing the hybrid ML-KEM zero-point downgrade (GHSA-w3jg-pjxf-73p4)

- Bump `russh` 0.62.7 → 0.63.0 in `cmdr-sftp` (resolves to 0.63.3, 2026-09-09). Fixes GHSA-w3jg-pjxf-73p4 (a malicious server could zero the X25519 half of `mlkem768x25519-sha256`, first patched in 0.63.0), plus GHSA-47hw-gvq5-r2gm (client handler callbacks reachable with unopened channel IDs) and GHSA-p8qx-h547-fjw9 (MAC-less negotiation panicking the session) from 0.63.1.
- `Handler::check_server_key` now takes `PublicKeyOrCertificate`. `presented` judges a certificate by the key inside it, the one russh verified the exchange signature against; we advertise no certificate algorithms, so in practice only bare keys arrive. New unit test `no_host_certificate_algorithm_is_advertised` guards that assumption, and `cmdr-sftp/DETAILS.md` says why.
- `russh`'s vendored `internal-russh-num-bigint` became upstream `num-bigint` 0.5.1, so `THIRD-PARTY-NOTICES.md` and `third-party-packages.gen.json` are regenerated.
- Supersedes Renovate PR #346.
@renovate renovate Bot changed the title fix(deps): update rust crate russh to 0.63.0 [security] fix(deps): update rust crate russh to 0.63.0 [security] - autoclosed Oct 1, 2026
@renovate renovate Bot closed this Oct 1, 2026
@renovate
renovate Bot deleted the renovate/crate-russh-vulnerability branch October 1, 2026 11:26
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.

0 participants