russh-extra
High-level async SSH APIs for Rust, built directly on top of
russh.
russh-extra provides ergonomic client, server, authentication, known-hosts,
command execution, shell, subsystem, SFTP, and forwarding APIs without requiring
application code to manage low-level russh handlers and channel messages for
common workflows.
The 0.1 line targets the main high-level SSH workflows. It is not a complete
wrapper for every low-level russh hook or control method; advanced users can
use the raw russh handle escape hatch when a workflow is not yet represented
by the high-level API.
This crate is not an official russh project.
Quick Start
Add russh-extra to your Cargo.toml:
[]
= { = "0.1", = false, = ["client", "known-hosts", "aws-lc-rs"] }
Connect to an SSH server and run a command:
use Client;
async
For tests and controlled environments, explicit host-key opt-out is available
via HostKeyPolicy::InsecureAcceptAny:
let session = builder
.endpoint
.username
.password
.accept_any_host_key // insecure: only for tests
.build
.connect
.await?;
Advanced users can access the underlying russh client handle:
let mut raw = session.russh_handle.await?;
let mut channel = raw.channel_open_session.await?;
channel.exec.await?;
Authentication
Credentials are attempted in the order configured by the builder. Passwords,
passphrases, and private key bytes are redacted from Debug output.
Password Authentication
let known_hosts = load?;
let session = builder
.endpoint
.username
.password
.known_hosts
.build
.connect
.await?;
Multiple Methods
let session = builder
.endpoint
.username
.identity
.agent
.password
.try_pinned_host_key_sha256?
.build
.connect
.await?;
agent() uses $SSH_AUTH_SOCK on Unix platforms when the agent feature is
enabled. On platforms without Unix-domain agent sockets it returns
AuthenticationErrorKind::Unavailable.
Known Hosts and Host Key Verification
Host-key verification defaults to strict rejection. Unknown host keys are rejected unless the caller configures a pinned SHA256 fingerprint, a known-hosts store, trust-on-first-use, or the explicit insecure accept-any policy.
Known Hosts File
let known_hosts = load?;
let session = builder
.endpoint
.username
.known_hosts
.build
.connect
.await?;
Trust on First Use
let known_hosts = new;
let session = builder
.endpoint
.username
.known_hosts_accept_new
.build
.connect
.await?;
known_hosts.save?;
Trust-on-first-use accepts an unknown key and adds it to the in-memory store.
Changed keys are rejected. Call KnownHosts::save() explicitly to persist the
store.
Hashed known-hosts entries are currently skipped with parse warnings.
@revoked entries reject matching host keys.
Command Execution
Session::command() returns bytes and exit metadata, not only a string:
let output = session.command.await?;
if output.success else
Buffered stdout and stderr have configurable per-command limits.
Shells and Subsystems
Enable the shell feature for interactive shells, PTY allocation, resize, and
generic subsystem channels:
let mut shell = session
.shell
.pty
.env
.build
.open
.await?;
shell.write_all.await?;
let mut buf = ;
let n = shell.read.await?;
println!;
shell.resize.await?;
shell.close.await?;
// For tokio::io integration, convert to AsyncRead + AsyncWrite:
let mut async_io = session
.shell
.pty
.build
.open
.await?
.into_async_io
.await?;
copy.await?;
Subsystem channels use the same streaming handle. For a higher-level SFTP
experience, enable the sftp feature and use Session::sftp() (see the
SFTP section below).
Port Forwarding
Enable the tunnel feature for local TCP forwarding, remote TCP forwarding,
one-shot direct TCP channels, and StreamLocal (Unix-domain) forwarding.
Local Forwarding
let tunnel = session
.tunnel
.start
.await?;
println!;
tunnel.close.await?;
Direct TCP
let mut stream = session
.direct_tcp
.open
.await?;
stream.write_all.await?;
stream.close.await?;
Remote Forwarding
let tunnel = session
.tunnel
.start
.await?;
println!;
tunnel.close.await?;
StreamLocal (Unix-Domain) Forwarding
let tunnel = session
.tunnel
.start
.await?;
println!;
tunnel.close.await?;
One-shot direct StreamLocal channels:
let mut stream = session
.direct_streamlocal
.open
.await?;
stream.write_all.await?;
stream.close.await?;
StreamLocal forwarding is available on Unix platforms when the tunnel feature
is enabled.
Server
Servers authenticate users, route commands, and manage shutdown explicitly.
let host_key = from_private_key;
let server = builder
.listen
.host_key
.password_auth
.exec
.build?;
server.run_until.await?;
The server API also supports public-key authentication, keyboard-interactive authentication, streaming exec handlers, shell/PTY/subsystem hooks, environment-variable propagation, forwarding authorization hooks, lifecycle hooks, and graceful shutdown handles.
SFTP
Enable the sftp feature for native SFTP v3 client operations over SSH
subsystem channels:
let sftp = session.sftp.await?;
let metadata = sftp.metadata.await?;
println!;
let mut dir = sftp.opendir.await?;
while let Some = sftp.readdir.await?
dir.close.await?;
let contents = sftp.read_to_vec.await?;
The SFTP client supports open, read, write, close, stat, lstat, fstat, setstat, fsetstat, opendir, readdir, remove, rename, mkdir, rmdir, realpath, readlink, and symlink operations. File and directory handles auto-close on drop.
Server-side SFTP is available via the SftpServerHandler trait when both
server and sftp features are enabled:
use ;
;
Feature Flags
| Feature | Default | Description |
|---|---|---|
client |
yes | Client connect, authentication, command execution, and session APIs |
known-hosts |
yes | Known-hosts parser, in-memory store, and client integration |
aws-lc-rs |
yes | russh crypto backend via aws-lc-rs |
server |
no | Server listener, auth callbacks, exec routing, lifecycle hooks |
shell |
no | Interactive shell, PTY, X11 forwarding, agent forwarding, subsystems |
tunnel |
no | TCP and StreamLocal (Unix-domain) forwarding, direct channels |
agent |
no | SSH agent authentication using $SSH_AUTH_SOCK on Unix |
sftp |
no | Native SFTP v3 client and server (SftpClient + SftpServerHandler trait) |
ring |
no | Alternative russh crypto backend via ring |
flate2 |
no | SSH compression support from russh |
rsa |
no | RSA key algorithm support from russh |
serde |
no | Serde serialization for config types |
full |
no | All stable runtime features |
Feature-gate checks:
Error Handling
russh-extra uses typed errors so callers can distinguish transport,
authentication, host-key, channel, command, forwarding, timeout, and
unsupported-operation failures:
match session.command.await
Tracing
russh-extra uses the tracing facade for connection, authentication,
channel, command, server, shell, and forwarding lifecycle events. Secrets,
private keys, passphrases, command stdin, and stream payloads are not logged.
use EnvFilter;
fmt
.with_env_filter
.init;
Set RUST_LOG=russh_extra=debug to see lifecycle events.
Security Policy
Host-key checking defaults to strict rejection. accept_any_host_key() uses
HostKeyPolicy::InsecureAcceptAny, an explicit unsafe opt-out for tests and
controlled environments.
Passwords, passphrases, private key material, and command stdin are never
logged or exposed in Debug output. See SECURITY.md and
docs/dev/security.md for the full policy.
Current Status
This repository is pre-1.0 and AI-driven.
Implemented in the 0.1 line:
- Client connect with password, private-key, agent, and keyboard-interactive authentication.
- Strict, pinned SHA256, and known-hosts host-key verification.
- Trust-on-first-use in the in-memory known-hosts store.
- Changed and revoked host-key rejection for known-hosts entries.
- Buffered
Session::command()with stdout/stderr capture, stdin, limits, and exit metadata. - Explicit
Session::disconnect()for graceful client-side connection teardown. - Server listener, password auth, public-key auth, keyboard-interactive auth, exact command routing, streaming exec, env propagation, lifecycle hooks, and graceful shutdown.
- Interactive shell, PTY allocation, resize, signal, X11 forwarding, agent forwarding, and subsystem channel opening.
- Native SFTP v3 client: open, read, write, close, stat, lstat, opendir, readdir, remove, rename, mkdir, rmdir, realpath, readlink, symlink.
- Direct TCP channels, local TCP forwarding, remote TCP forwarding, and StreamLocal (Unix-domain) forwarding.
- OpenSSH certificate authentication (certificate + private key pairs).
- Authentication banner display and server-side banner configuration.
- Structured tracing spans on connect, command, disconnect, and server run entry points.
- Typed error taxonomy and local loopback test fixtures.
- 14 example programs covering client, server, shell, subsystem, known-hosts, SFTP, and forwarding workflows.
Primary client, server, SFTP, and TCP forwarding paths are covered by local loopback integration tests. Some advanced Unix StreamLocal paths have implementation coverage and remain a hardening target for additional runtime tests.
Not yet implemented:
- Hashed hostname known-hosts matching and writing.
- Wildcard hostname known-hosts matching.
- Dynamic SOCKS-style forwarding.
- SFTP v4+ extensions.
- First-class high-level wrappers for every
russhcontrol surface. Current gaps include some low-level client controls such as rekey/keepalive/ping and no-more-sessions requests, and lower-level server hooks such as signal and DH GEX group lookup.
Examples
The crates/russh-extra/examples/ directory contains working example programs:
| Example | Feature flags | Description |
|---|---|---|
client_exec |
client |
Remote command execution with password auth |
client_exec_password |
client |
Password auth with explicit credential |
client_private_key |
client, known-hosts |
Private key auth with known-hosts verification |
client_shell |
client, shell |
Interactive shell with PTY allocation |
client_subsystem |
client, shell |
Raw SSH subsystem channel |
client_known_hosts |
client, known-hosts |
Known-hosts loading, TOFU, and saving |
client_sftp |
client, sftp |
SFTP file read, upload, and directory listing |
local_forward |
client, tunnel |
Local TCP port forwarding |
remote_forward |
client, tunnel |
Remote TCP port forwarding |
server_exec |
server |
Server with exec routing |
server_password |
server |
Server with password auth and exec routing |
server_public_key |
server |
Server with public key authentication |
server_streaming_exec |
server |
Server with streaming exec handlers |
tracing |
client, known-hosts |
Tracing instrumentation with env-filter |
Each example uses environment variables for configuration. Run with:
SSH_HOST=localhost SSH_PORT=2222 SSH_USER=test SSH_PASSWORD=secret \
Workspace
| Crate | Purpose |
|---|---|
russh-extra |
User-facing high-level API |
russh-extra-core |
Shared SSH domain types and errors |
russh-extra-test-support |
Integration test helpers (not published) |
russh-extra-tests |
Workspace-level tests (not published) |
MSRV
Minimum supported Rust version: 1.95.
Development
Or run commands directly:
The repository is the source of truth for goals, constraints, design decisions, and implementation status:
AGENTS.mdandCLAUDE.mddefine agent-facing commands and architecture.docs/dev/project-charter.mddefines the project goal and operating model.docs/dev/constraints.mddefines dependency, API, security, and testing constraints.docs/dev/ai-workflow.mdcontains reusable prompts and handoff rules.docs/dev/testing.mddefines the local test strategy.docs/dev/development-plan.mddefines phase gates and current work.docs/dev/security.mdanddocs/dev/release.mddefine security and compatibility rules.docs/dev/roadmap.mdtracks accepted work.docs/dev/design/contains guide-level design docs for non-trivial public API changes..agents/skills/contains local development skills.
License
This project is licensed under either the MIT license or the Apache License, Version 2.0.