CVE-2026-116794
russh: Agent server never calls `Agent::confirm`, so keys added with the confirm constraint (`ssh-add -c`) sign without confirmation
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.3
russh: Agent server never calls `Agent::confirm`, so keys added with the confirm constraint (`ssh-add -c`) sign without confirmation
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116794- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-03
EUVDEUVD-2026-112267
8.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
EUVD-2026-112267- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-vp5q-ff62-jwqv
8.3
russh: Agent server never calls `Agent::confirm`, so keys added with the confirm constraint (`ssh-add -c`) sign without confirmation
Summary
The agent server in russh::keys::agent::server never calls Agent::confirm before signing with a key that was added with the confirm constraint. That constraint is SSH_AGENT_CONSTRAIN_CONFIRM (RFC 9987 §5.2.7.2), which is what ssh-add -c sends.
The call was replaced by a hard-coded true in 3e04597 ("Native async traits encore", #455), first released in 0.50.0-beta.12. Every release since, up to and including 0.64.1, signs requests for confirm-constrained keys without asking the Agent implementation. A user who added a key with -c is therefore never asked to confirm.
Details
Connection::try_sign notes that the key needs confirmation, then awaits a ready future instead of the agent (server.rs L348-L360 at v0.64.1):
let agent = if needs_confirm {
let (agent, ok) = {
let _pk = key.clone();
Box::new(futures::future::ready((agent, true)))
}
.await;
if !ok {
return Ok((agent, false));
}
agent
} else {
agent
};
Before 3e04597 the first statement was let (agent, ok) = agent.confirm(key.clone()).await;.
try_sign was checked in the published crates on crates.io:
- Every russh release from 0.50.0-beta.12 to 0.64.1 (52 versions) has the hard-coded
true. - russh 0.50.0-beta.8 to beta.11 call
agent.confirm. - Earlier russh releases have no agent server. It lived in russh-keys, and no russh-keys release is affected.
Not affected:
Agentimplementations that keep the defaultconfirm, which returnstrueanyway.- Checks made in
Agent::confirm_request, which is still called withMessageType::Signfor every sign request (L158-L159). - The agent client, and the SSH client and server.
PoC
- Add this test to
mod testinrussh/src/keys/mod.rs. It uses the module's existingIncominghelper andED25519_KEY. - Run
cargo test -p russh --lib keys::test::test_agent_confirms_keys_added_with_the_confirm_constraint.
#[tokio::test]
#[cfg(unix)]
async fn test_agent_confirms_keys_added_with_the_confirm_constraint() {
use std::sync::atomic::{AtomicUsize, Ordering};
// An agent whose user refuses every confirmation prompt.
#[derive(Clone)]
struct Refuse(std::sync::Arc<AtomicUsize>);
impl agent::server::Agent for Refuse {
fn confirm(
self,
_: std::sync::Arc<PrivateKey>,
) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
self.0.fetch_add(1, Ordering::SeqCst);
Box::new(futures::future::ready((self, false)))
}
}
let prompts = std::sync::Arc::new(AtomicUsize::new(0));
let dir = tempfile::tempdir().unwrap();
let agent_path = dir.path().join("agent");
let mut listener = tokio::net::UnixListener::bind(&agent_path).unwrap();
let agent = Refuse(prompts.clone());
tokio::spawn(async move {
agent::server::serve(
Incoming {
listener: &mut listener,
},
agent,
)
.await
});
let key = decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
let public = key.public_key().clone();
let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
let mut client = agent::client::AgentClient::connect(stream);
// What `ssh-add -c` sends.
client
.add_identity(&key, &[agent::Constraint::Confirm])
.await
.unwrap();
let signed = client
.sign_request(&AgentIdentity::from(public), None, b"data".to_vec())
.await;
let confirm_calls = prompts.load(Ordering::SeqCst);
assert!(
signed.is_err(),
"the agent signed with a…
- Record
GHSA-vp5q-ff62-jwqv- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
NVD
8.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116794- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-862
- Updated
- 2026-11-05
OSV
8.3
russh: Agent server never calls `Agent::confirm`, so keys added with the confirm constraint (`ssh-add -c`) sign without confirmation
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116794- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-05
OSVDEBIAN-CVE-2026-116794
8.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
DEBIAN-CVE-2026-116794- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Updated
- 2026-11-05
OSVGHSA-vp5q-ff62-jwqv
8.3
russh: Agent server never calls `Agent::confirm`, so keys added with the confirm constraint (`ssh-add -c`) sign without confirmation
Summary
The agent server in russh::keys::agent::server never calls Agent::confirm before signing with a key that was added with the confirm constraint. That constraint is SSH_AGENT_CONSTRAIN_CONFIRM (RFC 9987 §5.2.7.2), which is what ssh-add -c sends.
The call was replaced by a hard-coded true in 3e04597 ("Native async traits encore", #455), first released in 0.50.0-beta.12. Every release since, up to and including 0.64.1, signs requests for confirm-constrained keys without asking the Agent implementation. A user who added a key with -c is therefore never asked to confirm.
Details
Connection::try_sign notes that the key needs confirmation, then awaits a ready future instead of the agent (server.rs L348-L360 at v0.64.1):
let agent = if needs_confirm {
let (agent, ok) = {
let _pk = key.clone();
Box::new(futures::future::ready((agent, true)))
}
.await;
if !ok {
return Ok((agent, false));
}
agent
} else {
agent
};
Before 3e04597 the first statement was let (agent, ok) = agent.confirm(key.clone()).await;.
try_sign was checked in the published crates on crates.io:
- Every russh release from 0.50.0-beta.12 to 0.64.1 (52 versions) has the hard-coded
true. - russh 0.50.0-beta.8 to beta.11 call
agent.confirm. - Earlier russh releases have no agent server. It lived in russh-keys, and no russh-keys release is affected.
Not affected:
Agentimplementations that keep the defaultconfirm, which returnstrueanyway.- Checks made in
Agent::confirm_request, which is still called withMessageType::Signfor every sign request (L158-L159). - The agent client, and the SSH client and server.
PoC
- Add this test to
mod testinrussh/src/keys/mod.rs. It uses the module's existingIncominghelper andED25519_KEY. - Run
cargo test -p russh --lib keys::test::test_agent_confirms_keys_added_with_the_confirm_constraint.
#[tokio::test]
#[cfg(unix)]
async fn test_agent_confirms_keys_added_with_the_confirm_constraint() {
use std::sync::atomic::{AtomicUsize, Ordering};
// An agent whose user refuses every confirmation prompt.
#[derive(Clone)]
struct Refuse(std::sync::Arc<AtomicUsize>);
impl agent::server::Agent for Refuse {
fn confirm(
self,
_: std::sync::Arc<PrivateKey>,
) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
self.0.fetch_add(1, Ordering::SeqCst);
Box::new(futures::future::ready((self, false)))
}
}
let prompts = std::sync::Arc::new(AtomicUsize::new(0));
let dir = tempfile::tempdir().unwrap();
let agent_path = dir.path().join("agent");
let mut listener = tokio::net::UnixListener::bind(&agent_path).unwrap();
let agent = Refuse(prompts.clone());
tokio::spawn(async move {
agent::server::serve(
Incoming {
listener: &mut listener,
},
agent,
)
.await
});
let key = decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
let public = key.public_key().clone();
let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
let mut client = agent::client::AgentClient::connect(stream);
// What `ssh-add -c` sends.
client
.add_identity(&key, &[agent::Constraint::Confirm])
.await
.unwrap();
let signed = client
.sign_request(&AgentIdentity::from(public), None, b"data".to_vec())
.await;
let confirm_calls = prompts.load(Ordering::SeqCst);
assert!(
signed.is_err(),
"the agent signed with a…
- Record
GHSA-vp5q-ff62-jwqv- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-116794
8.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.50.0-beta.12 until 0.65.0, the agent server's Connection::try_sign path in russh/src/keys/agent/server.rs recognizes SSH_AGENT_CONSTRAIN_CONFIRM but returns a hard-coded successful result instead of calling Agent::confirm. In applications that override Agent::confirm to require user approval or another policy check, a process that can reach the agent socket, including the remote side of a forwarded agent, can use a key added through ssh-add -c without that check and can authenticate or sign data as the user while the key remains loaded. The separate Agent::confirm_request hook still runs for every signing request, so checks implemented there remain effective, and implementations that retain the default always-successful Agent::confirm do not lose an approval check through this regression. This issue is fixed in version 0.65.0.
- Record
UBUNTU-CVE-2026-116794- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:H/SI:H/SA:N - Updated
- 2026-11-05
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
russhcrates.io |
before 0.65 | 0.65 |
rust-russhDebian:14 |
All versions, plus listed versions 0.57.1-10.57.1-20.64.1-10.64.1-2 |
None known |
rust-russhUbuntu:26.10 |
All versions, plus listed versions 0.57.1-2 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases, references | OSV | |
| facts | EUVD, NVD | |
| references | CVE List | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-116794 is a high vulnerability (CVSS 8.3) in Eugeny Russh, published October 28, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.16%, the 4th percentile. Fixed in russh 0.65.
References
github.com/Eugeny/russh/security/advisories/GHSA-vp5q-ff62-jwqv
github.com/Eugeny/russh/commit/3e04597dc54e36bfc85c21996f7d8d9d224e9826
github.com/Eugeny/russh/commit/62db6e7defd6d244159da962ccccb029e785ce0f
github.com/Eugeny/russh/releases/tag/v0.65.0
nvd.nist.gov/vuln/detail/CVE-2026-116794
github.com/advisories/GHSA-vp5q-ff62-jwqv
Left out here: the rest of 2 source descriptions. The JSON record has them.