Skip to content

fix: unknown request types should return failure - #112

Open
cquintana92 wants to merge 1 commit into
wiktor-k:mainfrom
cquintana92:fix/unknown-request-types-return-failure
Open

fix: unknown request types should return failure#112
cquintana92 wants to merge 1 commit into
wiktor-k:mainfrom
cquintana92:fix/unknown-request-types-return-failure

Conversation

@cquintana92

@cquintana92 cquintana92 commented Aug 7, 2026

Copy link
Copy Markdown

Fix: Reply SSH_AGENT_FAILURE to unknown request types instead of closing the connection

Issue

When a client sends a request whose message type is not recognised, the agent closes the connection without replying. Per draft-miller-ssh-agent-14 § 3.1:

SSH_AGENT_FAILURE messages are also sent in reply to requests with unknown types.

OpenSSH's ssh-agent behaves that way, but agents built on this crate currently do not, because the connection is dropped at the codec layer before any session handling runs.

Impact

Some clients probe the agent with a legacy request type that is not implemented by modern agents. For example Ruby's net-ssh opens negotiation with SSH2_AGENT_REQUEST_VERSION (message type 1). When the agent closes the socket instead of replying, net-ssh raises a FrozenError and deploy tools built on it cannot connect at all, even though ssh/ssh-add work fine against the same agent.

Root cause

The framing Codec decodes every incoming frame into a Request before the Session is invoked. For an unknown message type, Request::decode returned Err(UnsupportedCommand). That decoder error is fatal to the stream: tokio_util's FramedImpl enters an errored state after a decode error and yields EOF on the next poll, so the socket is torn down before Session::handle is ever called. No Session override can prevent this with the current code.

Proposed solution

  • Adding a Request::Unknown(u8) variant that captures the raw message type byte. Unknown message types now decode successfully (the payload following the type byte is skipped, bounded by themessage length prefix), encode back to the original type byte, and report the correct message_id.
  • In the default Session::handle implementation, respond to Request::Unknown with Response::Failure (i.e. SSH_AGENT_FAILURE, message type 5) and keep the connection open, matching OpenSSH's behaviour.

The default implementation suits all existing agents; because handle is the documented override point, users who need custom handling for a particular unknown type can still intercept it themselves.

Testing

New integration test tests/unknown_request.rs starts a real agent over a Unix socket, replays the exact net-ssh probe (SSH2_AGENT_REQUEST_VERSION, type 1, body "2.0"), and asserts that:

  1. The agent replies with SSH_AGENT_FAILURE (message type 5) instead of closing the connection.
  2. The connection remains usable for a subsequent supported request (SSH_AGENTC_REQUEST_IDENTITIES).

The test fails against the previous behaviour (connection closed without a reply) and passes with this change.

Validation

  • cargo test reports all suites pass (unit, roundtrip, doc, new integration test)
  • cargo clippy --workspace --no-deps --all-targets -- -D warnings comes clean
  • cargo fmt --check comes clean

Compatibility

This is a backward-compatible additive change: Request gains one new variant (the compiler will flag exhaustive matches, which is desirable since callers must now decide how to handle unknown messages). No existing behaviour changes.

@cquintana92
cquintana92 force-pushed the fix/unknown-request-types-return-failure branch 2 times, most recently from f66c5ad to 81948a9 Compare August 7, 2026 10:49
Signed-off-by: Carlos Quintana <carlos@cquintana.dev>
@cquintana92
cquintana92 force-pushed the fix/unknown-request-types-return-failure branch from 81948a9 to 5089da4 Compare August 12, 2026 14:12
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