Open actionable advisories and recorded vendor fixes. This public package view has no inventory attached and cannot establish whether your deployment is affected. A vendor fix never resolves your finding; verify your installed version and the source guidance.
Open advisories
23 actionable advisories, newest first. Withdrawn advisories and news are excluded. Coverage is partial.
All evidence, reproduction steps, and scripts in the report were verified against a real running instance. Nothing is fabricated or synthetic; every verdict is backed by captured output from an actual deployment. The SSH agent server that ships with russh (`russh::keys::agent::server::serve`) parses client key-addition requests and, for each `SSH_AGENT_CONSTRAIN_CONFIRM` (0x02) record it finds, pushes one `Constraint::Confirm` value into a per-key vector inside an unbounded loop, then stores the resulting pair permanently in its keystore (`russh/src/keys/agent/server.rs:296-303`). Neither the constraint vector nor the keystore has a size or count limit: the only boundary is the 256 KiB agent frame cap, and every 1-byte record a client sends becomes a 56-byte enum retained for the life of the process. Any process that can connect to the agent socket - the same trust model the project itself established when fixing a prior advisory about this same request handler - can…
Recommended action: Upgrade to 0.65.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
russhkeys::agent::server::addkey (russh/src/keys/agent/server.rs:271-319) spawns a dedicated tokio task for every 5-byte SSHAGENTCONSTRAINLIFETIME record attached to an added key, each cloning the key blob and sleeping for the declared lifetime (up to ~136 years). The loop has no per-request task budget, and spawned tasks are not rolled back when the overall request fails. Repeating one constrained add-key message over an agent connection converts wire bytes into resident tasks and heap at roughly 141x amplification: in our measurement 10.2 MiB of protocol traffic produced ~2,096,120 live tasks and +1,447,856 KiB RSS, while the same traffic sent as unconstrained add-key requests cost +268 KiB. An attacker reaching the agent socket (a local process, or a remote SSH server the user connects to with agent forwarding, since russh clients accept the auth-agent@openssh.com channel by default at russh/src/client/mod.rs:2686) can exhaust the agent process's memory and task capacity…
Recommended action: Upgrade to 0.64.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Details In russh/src/server/encrypted.rs, serverreadauthrequestpk (0.63.3, around lines 880-990): The requested algorithm name (pubkeyalgo) is read and only used to decode the key: PublicKeyOrCertificate::decode(&pubkeyalgo, &pubkeykey). The derived hashalg is then dropped (PublicKeyOrCertificate::PublicKey { ref key, .. }). The signature blob is decoded (Signature::decode) and verified with Verifier::verify(&pubkey, &buf, &sig). ssh-key picks the hash from the signature's own algorithm (sig.algorithm()). pubkeyalgo and sig.algorithm() are never compared. So for an RSA key, the request can say rsa-sha2-512 while the signature is ssh-rsa. The signature is valid for SHA-1, verification succeeds, and Handler::authpublickey is called. Affected versions: every russh release, from the first (0.34.0-beta.1, 2022-03) through 0.63.3 and current main. The behaviour is inherited from thrussh, which russh was forked from. The mechanism changed over time, but the outcome is the same: the hash is…
Recommended action: Upgrade to 0.64.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
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 SSHMSGKEXINIT) but never sends the follow-up SSHMSGKEXECDHINIT, 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 SSHMSGCHANNELOPEN the peer sends is processed inline and appends one reply to an unbounded internal queue (priorityreceiver, 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…
Recommended action: Upgrade to 0.63.2 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
SshBlockCipher implementations (AES-CBC, AES-CTR, 3DES-CBC, etc.) report needsmac() == true, meaning they are documented/intended to always be paired with a separate integrity MAC. However, key-exchange negotiation only checks needsmac() 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 packetlengthtoreadforblocklength() bytes up front (16 bytes for any SshBlockCipher) into buffer.buffer. 2. Decrypts the first block to recover the…
Recommended action: Upgrade to 0.63.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
CVE-2026-68930 was fixed by adding Session::isestablishedchannel() in russh/src/server/encrypted.rs, which gates every channel-scoped SERVER-side message (CHANNELREQUEST, CHANNELDATA, CHANNELEOF, CHANNELCLOSE, CHANNELWINDOWADJUST, CHANNELEXTENDEDDATA) on enc.channels.get(&channel).issomeand(|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 clientreadauthenticated (russh/src/client/encrypted.rs, ~lines 431-757), for CHANNELDATA, CHANNELEXTENDEDDATA, CHANNELEOF, CHANNELCLOSE, CHANNELOPENFAILURE, CHANNELSUCCESS, CHANNELFAILURE, and the CHANNELREQUEST 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(&channelnum) { ... } (a no-op if the channel is unknown),…
Recommended action: Upgrade to 0.63.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Vulnerability The hybrid ML-KEM 768 + X25519 key exchange implementation in russh/src/kex/hybridmlkem.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 (serverdh, lines 92-93): Client-side (computesharedsecret, lines 154-155): 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: - serverdh line 77: if clientpubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); } - computesharedsecret line 122: if remotepubkey.0 == [0u8; 32] { return Err(crate::Error::Kex); } The same X25519 scalar multiplication pattern appears in hybridmlkem.rs, but the fix was not applied there.…
Recommended action: Upgrade to 0.63.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Details Affected versions and vulnerable location - Confirmed present on default branch main at HEAD 0089c89c94753bebbec12b956c07a1cd38740379. - Crate version at HEAD: 0.62.4. - Vulnerable locations on current default branch: - russh/src/server/mod.rs:91 (pub maxauthattempts: usize) - russh/src/server/mod.rs:121 (default maxauthattempts: 10) - russh/src/server/encrypted.rs:89 (USERAUTHREQUEST dispatch into auth handler path) - russh/src/server/encrypted.rs:98 (self.common.authattempts += 1) - russh/src/server/encrypted.rs:53 (only runtime read of authattempts, used for initial reject timing, not attempt limiting) - Default-branch history check did not show a newer merged commit adding enforcement against config.maxauthattempts. Reachability trace verified 1. Entry point: exported server API server::runstream in russh/src/server/mod.rs:1049. 2. Session run loop in russh/src/server/session.rs processes incoming packets and calls reply(...) (server/session.rs:725). 3. reply forwards…
Recommended action: Upgrade to 0.62.6 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
There is a server-side channel state issue in russh. After a client is authenticated, russh can dispatch channel-scoped handler callbacks for recipient channel IDs that were never opened or confirmed. In the strongest reproduced case, the client does not send SSHMSGCHANNELOPEN at all. It authenticates normally, then sends SSHMSGCHANNELREQUEST packets with request type exec for a range of recipient channel IDs. russh still calls the server application's execrequest handler. This is not an authentication bypass. A valid login is required. The issue is that the SSH channel lifecycle is not enforced before channel-scoped callbacks are delivered to the application. Impact An authenticated client can bypass the server application's channel-open policy. A server may deny session channels by returning false from Handler::channelopensession. A server may also assume that callbacks such as execrequest, shellrequest, subsystemrequest, data, channeleof, or channelclose are only delivered for…
Recommended action: Upgrade to 0.62.5 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
A malicious SSH server can crash a russh client session with a single malformed key-exchange reply, causing a pre-authentication Denial-of-Service before the server host key is verified. The embedding process itself stays up, but the connection is killed deterministically. Details Every other kex path in russh validates the peer ephemeral length before cloning: - Curve25519Kex::serverdh (russh/src/kex/curve25519.rs:61-65) checks if pubkeylen != 32 { return Err(crate::Error::Kex); } before clonefromslice. - The hybrid ML-KEM, ECDH-NIST, and DH/GEX paths all validate lengths. Only the client-side curve25519 computesharedsecret is missing the check. This asymmetric validation gap makes the bug easy to miss in code review: a malicious client cannot panic a russh server this way (the server path checks the length), but a malicious server can panic a russh client. Incriminated source code (repo-relative paths): - Vulnerable computesharedsecret: russh/src/kex/curve25519.rs:110-117…
Recommended action: Upgrade to 0.62.4 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
A post-authentication denial-of-service panic in russh 0.62.2 (commit c4be19f1915c8682f4615c3fd50008512b474491, current default branch main as of 2026-07-22). An authenticated client sends a pty-req channel request carrying more than 130 terminal-mode records. The parser uses a fixed [(Pty::TTYOPEND, 0); 130] array but increments its counter i for every valid record (logging "too many pty codes" without returning), then slices &modes[0..i] — an out-of-bounds slice that panics (range end index 131 out of range for slice of length 130) before the application ptyrequest handler runs. This is reachable with the default server configuration and the default crypto config (curve25519-sha256 + chacha20-poly1305), requiring only an authenticated session channel — no caller-supplied parameter. It is reproduced end-to-end against the unmodified real russh 0.62.2 library (a real russh::client + russh::server over TCP, using the public Channel::requestpty(...) API); the PoC below links…
Recommended action: Upgrade to 0.62.4 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
A pre-authentication denial-of-service panic in russh 0.62.2 (commit c4be19f1915c8682f4615c3fd50008512b474491, current default branch main as of 2026-07-22). An unauthenticated client sends a single SSHMSGKEXECDHINIT whose QC is 32 zero bytes. russh's Curve25519 KEX does not reject the all-zero peer public value, so serverdh() computes the all-zero shared secret and computeexchangehash() then calls encodempint(&shared.0, ...), which indexes s[i] at i == s.len() and panics (index out of bounds: the len is 32 but the index is 32) before host-key signature verification. The server KEX task dies on the first KEX message, before authentication. This is reachable with the default server configuration (Config::default() → Preferred::DEFAULT, whose kex list includes curve25519-sha256) and requires no caller-supplied parameter. It is reproduced end-to-end against the unmodified real russh 0.62.2 library (a real server + raw TCP client over TCP); the PoC below links the real crate, not a…
Recommended action: Upgrade to 0.62.4 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
SSH message fields were decoded through allocation-first parsers before field-specific bounds Summary Several russh client and server message handlers decoded attacker-controlled SSH strings, name-lists, and byte fields into owned allocations before applying field-specific bounds. A remote SSH peer could send oversized, high-fanout, or malformed length-prefixed fields and make the library allocate, attempt to allocate, or split data before rejecting input that should have been rejected earlier. Affected Versions Oldest verified exploitable stable release: russh 0.34.0. - Historical stronger case: russh = 0.34.0, < 0.58.0. These releases have the allocation-first KEXINIT field parsing issue and still use CryptoVec for inbound packet/decompression buffers. A peer can combine negotiated RFC zlib, rekey, compressed KEXINIT expansion, historical CryptoVec decompression growth, and KEXINIT name-list fanout. - Current maintained-line case: russh = 0.58.0, including 0.60.2. These releases…
Recommended action: Upgrade to 0.61.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
russh did not enforce the SSH identification-string rules as deliberately as OpenSSH. In particular, the server-side identification reader used the same permissive path as the client, allowing pre-banner lines from clients, and the reader did not enforce a bounded number of pre-banner lines. For a library server built on russh, this could allow a remote peer to hold connection setup resources in the cleartext pre-authentication phase with malformed identification input that should have been rejected early. Details RFC 4253 section 4.2 defines the SSH protocol version exchange. The identification string is a single line terminated by CR LF, must fit within 255 characters including CR LF, and clients should not send pre-banner lines before their SSH identification string. Before the fix, russh's identification reader lived in: - russh/src/sshread.rs - russh/src/server/mod.rs The same readsshid() behavior was used for both client and server contexts. That allowed server-side…
Recommended action: Upgrade to 0.61.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
In the russh client keyboard-interactive authentication path, a malicious SSH server could send a USERAUTHINFOREQUEST with an attacker-controlled prompt count, and the client would use that raw count directly in Vec::withcapacity(...) before validating that enough prompt data was actually present in the packet. This is a client-side denial-of-service / resource-exhaustion issue on the keyboard-interactive auth path. Details The vulnerable code path is in: - russh/src/client/encrypted.rs When the client is in CurrentRequest::KeyboardInteractive state and receives SSHMSGUSERAUTHINFOREQUEST, it parses: 1. name 2. instructions 3. language tag 4. nprompts Before the fix, the code then did: That means a malicious server could advertise an enormous nprompts value even if the packet contained no prompt bodies at all. The fix rejects inconsistent prompt counts before allocating: Each prompt needs at least 4 bytes of string length plus 1 byte of echo flag, so remaininglen() / 5 is a…
Recommended action: Upgrade to 0.61.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
The russh server authentication path keeps internal userauth state across SSHMSGUSERAUTHREQUEST messages without separating that state when the request principal changes. RFC 4252 allows the user name and service name fields to change between authentication requests. The issue is not that such changes are invalid. The issue is that russh-owned authentication state, such as remaining methods, partial-success state, and in-progress method state, can remain associated with the connection and then influence a later request for a different (user, service). This is an internal library state mismatch. Applications are responsible for any authentication state they keep in their own handlers, but russh must reset or separate state that russh itself owns. Details The relevant server-side auth logic is in: - russh/src/server/encrypted.rs - russh/src/auth.rs RFC 4252 section 5 says the user name and service name fields are repeated in every SSHMSGUSERAUTHREQUEST and may change. It also…
Recommended action: Upgrade to 0.61.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
When SSH compression is enabled, russh accepted compressed packets whose on-wire size passed the normal transport packet-length checks but whose decompressed size was much larger. This allowed a remote peer to send oversized post-decompression packets that should have been rejected. In current releases, this is a remote denial-of-service / resource-exhaustion issue in the post-decompression receive path. In older releases before 0.58.0, the same remote decompression path used CryptoVec, which appears to make the historical impact worse. Details The normal SSH transport read path enforces a packet-length limit before the packet body is read: - russh/src/cipher/mod.rs However, RFC 4253 compression is applied to the SSH payload field only. The packetlength field and MAC are computed over the compressed payload, so a packet that is reasonably sized on the wire can still expand to a much larger message body after decompression. In russh, compressed packet bodies are later…
Recommended action: Upgrade to 0.61.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Title Unchecked CryptoVec allocation and growth handling was reachable from local agent inputs in current russh releases and from remote SSH traffic in historical pre-0.58.0 releases Summary CryptoVec used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation/locking paths. In current russh releases, local SSH agent peers could still feed attacker-controlled frame lengths into buffer growth before validation. In older russh releases before 0.58.0, remote SSH traffic also reached CryptoVec through transport and compression buffers. Details The underlying unsafe paths were in CryptoVec: - cryptovec/src/cryptovec.rs - unchecked capacity growth - unchecked length arithmetic in growth callers - raw allocation and reallocation paths coupled to those sizes - cryptovec/src/platform/unix.rs - mlock / munlock previously accepted zero-length calls and performed null-pointer validation inside the unsafe OS-call path There are two relevant reachability stories: 1. current…
Recommended action: Upgrade to 0.60.3 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
A pre-authentication denial-of-service vulnerability exists in the server's keyboard-interactive authentication handler. A malicious client can crash any russh-based server that implements keyboard-interactive auth (e.g., for 2FA/TOTP) with a single malformed packet, requiring no credentials. Vulnerability Details In russh/src/server/encrypted.rs, the function readuserauthinforesponse decodes a u32 count from the client's SSHMSGUSERAUTHINFORESPONSE and passes it directly to Vec::withcapacity(): An attacker can send n = 0x10000000 (268M) or larger in a minimal packet (~50 bytes after encryption). The server attempts to allocate n ~24 bytes (size of Option<Bytes) = ~6.4GB, causing an OOM crash. Attack Flow 1. Attacker connects via TCP, completes key exchange (no credentials needed -- this is the anonymous DH handshake, not authentication) 2. Sends USERAUTHREQUEST with method keyboard-interactive 3. Server handler returns Auth::Partial with prompts (standard for 2FA/TOTP) 4.…
Recommended action: Upgrade to 0.60.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
The channel window adjust message of the SSH protocol is used to track the free space in the receive buffer of the other side of a channel. The current implementation takes the value from the message and adds it to an internal state value. This can result in a integer overflow. If the Rust code is compiled with overflow checks, it will panic. A malicious client can crash a server. Details According https://datatracker.ietf.org/doc/html/rfc4254#section-5.2, The value must not overflow. The incorrect handling is done in server/encrypted.rs and client/encrypted.rs in the handling of CHANNELWINDOWADJUST. It could be replaced with something like PoC A customized client code would be required to send a message with a big value like u32max. Not done yet. Impact This problem seems only critical to a server. One user can crash the server, which might take down the service. A malicious server could also crash a single client, but this seems not very critical.
Recommended action: Upgrade to 0.54.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Allocating an untrusted amount of memory allows any unauthenticated user to OOM a russh server. Details An SSH packet consists of a 4-byte big-endian length, followed by a byte stream of this length. After parsing and potentially decrypting the 4-byte length, russh allocates enough memory for this bytestream, as a performance optimization to avoid reallocations later. https://github.com/Eugeny/russh/blob/4eaa080e7532662023f75e8fff45b743fe607f8c/russh/src/cipher/mod.rs#L254 But this length is entirely untrusted and can be set to any value by the client, causing this much memory to be allocated, which will cause the process to OOM within a few such requests. RFC 4253 contains an explicit section on packet length limits: https://datatracker.ietf.org/doc/html/rfc4253#section-6.1 However, implementations SHOULD check that the packet length is reasonable in order for the implementation to avoid denial of service and/or buffer overflow attacks. PoC Running the echoserver example on…
Recommended action: Upgrade to 0.44.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Terrapin is a prefix truncation attack targeting the SSH protocol. More precisely, Terrapin breaks the integrity of SSH's secure channel. By carefully adjusting the sequence numbers during the handshake, an attacker can remove an arbitrary amount of messages sent by the client or server at the beginning of the secure channel without the client or server noticing it. Mitigations To mitigate this protocol vulnerability, OpenSSH suggested a so-called "strict kex" which alters the SSH handshake to ensure a Man-in-the-Middle attacker cannot introduce unauthenticated messages as well as convey sequence number manipulation across handshakes. Warning: To take effect, both the client and server must support this countermeasure. As a stop-gap measure, peers may also (temporarily) disable the affected algorithms and use unaffected alternatives like AES-GCM instead until patches are available. Details The SSH specifications of ChaCha20-Poly1305 (chacha20-poly1305@openssh.com) and…
Recommended action: Upgrade to 0.40.2 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Diffie-Hellman key validation is insufficient, which can lead to insecure shared secrets and therefore breaks confidentiality. Details Russh does not validate Diffie-Hellman keys. It accepts received DH public keys $e$ where $e<0$, $e=1$, or $e \geq p-1$ from a misbehaving peer annd successfully performs key exchange. This is a violation of RFC 4253, section 8 and RFC 8268, section 4, which state that: DH Public Key values MUST be checked and both conditions: - $1 < e < p-1$ - $1 < f < p-1$ MUST be true. Values not within these bounds MUST NOT be sent or accepted by either side. If either one of these conditions is violated, then the key exchange fails. For example, a DH client public key $e=1$ would mean that the shared secret that the server calculates is always $K = e^y \mod{p} = 1^y \mod{p} = 1$. In other cases, an insecure order-2 subgroup may be used. Also, the code does not look like it ensures that the generated secret key $y$ is in the valid interval $0 < y < q$ (or,…
Recommended action: Upgrade to 0.37.1 or 0.36.2 by release line, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.