russh-sftp
An async Rust SFTP client and server implementation built on top of Russh, providing a std::fs-like API over SSH file transfer channels.
Repository Health
Technical Analysis
russh-sftp is a pure-Rust, async implementation of the SFTP protocol (version 3 of the draft spec) that covers both client and server roles from the wire format up. Rather than binding to libssh2 like most Rust SFTP support, it hand-rolls the binary packet serialization for the protocol itself, then layers a std::fs-style client API and an overridable server Handler trait on top.
Because it only needs a stream implementing AsyncRead/AsyncWrite, it isn’t tied to Russh specifically and can run over any SSH subsystem channel or compatible transport, including a wasm32 target. It automatically negotiates common OpenSSH extensions such as limits@openssh.com and hardlink@openssh.com, and is already used by infrastructure tooling like kty, a terminal for Kubernetes.
What You Get
- A high-level async SftpSession client with std::fs-like methods for files and directories
- A low-level RawSftpSession for sending and receiving SFTP packets directly
- A server::Handler trait for implementing custom SFTP servers with per-operation overrides
- Automatic negotiation of common OpenSSH SFTP extensions (limits, hardlink, fsync, statvfs, expand-path)
Common Use Cases
- Adding file-transfer support to a Russh-based SSH server
- Building Rust CLI or service clients that transfer files over SFTP
- Powering infrastructure tools (e.g. Kubernetes terminals) that need file access over an SSH channel
- Compiling SFTP client logic to wasm32 for browser or edge environments
Under The Hood
Architecture The crate splits cleanly into client, server, and protocol layers. The client side has a two-tier design: RawSftpSession (in client/rawsession.rs) sends and receives individual SFTP packets, while SftpSession (client/session.rs) wraps it in an Arc-shared, std::fs-like API with fs/file.rs and fs/dir.rs providing File and ReadDir abstractions. The server side (server/handler.rs) exposes a Handler trait with a default “unimplemented” response for every SFTP operation, guarded by an optional async-trait feature for pre-RPITIT compatibility. All wire-format work is centralized in protocol/ (one file per packet type: open.rs, read.rs, write.rs, readdir.rs, etc.) with custom binary ser.rs/de.rs and buf.rs modules rather than a generic serializer, and error.rs unifies I/O, protocol, and client errors behind a single thiserror-based Error enum.
Tech Stack Built on Rust 2021 edition with tokio pulled in narrowly (io-util, rt, sync, time, macros only) plus tokio-util for its reactor, avoiding a full tokio dependency footprint. serde and serde_bytes handle structured (de)serialization glue around the hand-rolled binary framing, bitflags 2.x (with serde support) models OpenFlags, thiserror provides typed errors, chrono handles file timestamps, bytes backs buffer parsing, and dashmap tracks concurrent in-flight requests by id. An optional async-trait feature accommodates dyn-trait server handlers, and a cfg(target_arch = “wasm32”) dependency set (wasm-bindgen-futures, gloo-timers) enables browser targets. Dev-dependencies include russh itself (for the client/server examples), criterion for upload benchmarking, and env_logger/anyhow for example ergonomics.
Code Quality No unit or integration test files exist under src/ — the README’s own feature checklist explicitly lists “Unit tests” as an open item, so correctness currently rests on the working client/server examples and a criterion-based upload benchmark rather than automated tests. Error handling is explicit and typed throughout via the shared Error enum, with From impls converting io::Error and bytes::TryGetError into it rather than swallowing failures. Naming follows idiomatic Rust and mirrors the SFTP spec’s own packet names closely, and CI includes a dedicated RustSec advisory audit workflow (audit.yml via cargo-deny) triggered on Cargo.toml/Cargo.lock changes, though no clippy/rustfmt enforcement was found in the workflow files inspected.
API Design The public API favors familiarity: SftpSession’s open/create/metadata/read_dir/symlink methods read like std::fs, so consumers already comfortable with Rust’s filesystem API face little new surface to learn. On the server side, the Handler trait’s per-operation default of “unimplemented” means implementers only write the methods they actually support, without a large boilerplate impl. Extension negotiation (e.g. applying the server’s advertised max packet length from limits@openssh.com) happens automatically inside SftpSession::new, so callers get protocol-level optimizations without any manual capability checks.