Releases: lablup/bssh
Release list
v2.4.1
A patch release that closes the four items listed as known issues in the v2.4.0 release notes.
New Features
None. BSSH_CERT_AUTHORITY_POLICY=reject is new but is an opt-in hardening switch for existing behavior; see Improvements.
Improvements
accept-newkeeps a process-lifetime host key pin when no known_hosts path can be determined, instead of disabling verification entirely (#242). Previously, in containers and service environments whereHOMEis unset and no passwd entry supplies a home directory, the CLI default mode accepted any server key unconditionally. The first key seen for ahost:portis now pinned for the life of the process and a different key later in the same run is rejected. Nothing persists past the process, so this is still weaker than file-backed TOFU.- The known_hosts path is validated before it is treated as trust state (#242). A path that is a non-regular file, cannot be opened, or is a symlink that cannot be resolved now fails the connection and names which of the three it was, in both
accept-newand strict mode. Previously all three were indistinguishable from an absent file, soaccept-newtreated every host as a first use and strict mode treated every host as unknown. A genuinely absent file still behaves as an empty one. - First-use recording is serialized across bssh processes, not just within one (#242). A sibling advisory lock file (
known_hosts.lock, created 0600 under a 0700 parent) now wraps the check-then-record window, and the path probe, marker scan, and lookup all run again under the lock. Two bssh processes connecting to the same new host at once previously could both read known_hosts before either wrote and both append an entry. BSSH_CERT_AUTHORITY_POLICY=rejectmakes a matching known_hosts@cert-authorityline a hard rejection (#242). The default is unchanged: a match warns and falls through to ordinary TOFU, because bssh has no CA signature validation and failing closed would break every working CA setup with no workaround.
Bug Fixes
- Forwarding target hostnames are sent to the server instead of being resolved on the client (#257).
-Ltargets, SOCKS5-Ddomain requests, later jump-chain hops, and the destination behind a jump chain now go out in thedirect-tcpiprequest as written, and the remote sshd resolves them, matching OpenSSH. Any target whose name resolves only from the server's network position previously failed before a single byte of SSH traffic was sent, which is one of the primary reasons to use a bastion. Forcing-4or-6still resolves locally and sends a numeric address, since that is the only way the family request can affect a connection the server makes. - SOCKS4 dynamic forwarding honors the address family preference (#255). The SOCKS4 handler called a family-agnostic opener that hardcoded
AddressFamily::Any, so it ignored-4,-6, and the ssh_configAddressFamilykeyword while every other path in v2.4.0 honored them:bssh -6 -D 1080 user@hosthard-failed every other path when IPv6 was unavailable and still handed back a proxy that tunneled IPv4. A SOCKS4 request under forced IPv6 is now refused with the protocol's 0x5B reply before an SSH channel is opened.Anyand forced IPv4 are unchanged. - SOCKS5 parses IPv6 destination literals (#256). The ATYP 0x04 arm read none of the request body and replied 0x08 "address type not supported", so every SOCKS5 client sending an IPv6 destination was refused and
bssh -6 -D 1080was self-contradictory. The arm now consumes its 16 address bytes and 2 port bytes and forwards a bracketed[ipv6]:porttarget. - A socket address slice reports its first address as the hostname instead of a comma-joined list of every address (#243). A comma is the known_hosts host-list separator, so the joined form is rejected by the hostname validation added in v2.4.0.
- A known_hosts file created by the first recording no longer starts with a blank line (#243). Cosmetic; it was never a parse problem. Files that already existed are untouched.
CI/CD Improvements
None.
Technical Details
- Performance: the known_hosts write locks are skipped when the offered key already matches a recorded entry (#243). The marker scan and lookup run first without either lock and return on a definite match, so parallel connections to an already-recorded cluster no longer serialize on a lock they do not need. Both locks are still taken for a host that looks unknown, with the full check repeated under them so the fast path cannot race a concurrent first-time recording.
- Tests: the nondeterministic
test_expand_path_with_tildefailure under the full library suite is fixed (#243). Seven tests repoint the process-globalHOME, and cargo runs tests in parallel threads, soHOMEcould change betweenexpand_path's internaldirs::home_dir()call and the assertion's own call.EnvGuardnow takes a dedicatedHOMEmutex when it sets or removes that variable, andEnvGuard::lock_home()gives read-only tests the same lock. No library behavior changed. - Validation:
cargo fmt --all --check,cargo clippy --lib --tests --all-targets -- -D warnings,cargo test --lib(1386 passed, 0 failed, 9 ignored), and the focusedaddress_family_testandsocks_ipv6_literal_testsuites all pass on the tagged commit.
Dependencies
No Cargo.toml requirement changed, and no crate entered or left the graph. Seven resolved versions moved in Cargo.lock: aho-corasick 1.1.4 to 1.1.5, darling, darling_core, and darling_macro 0.23.0 to 0.24.0, data-encoding 2.11.0 to 2.11.1, instability 0.3.12 to 0.3.13, and line-clipping 0.3.7 to 0.3.8.
Breaking Changes
None. The library API changed additively only. ServerCheckMethod gained AcceptNewKnownHostsFile(String) and AcceptNewInMemory in both the bssh::ssh::tokio_client enum and its bssh::shared::auth_types mirror, with From conversions in both directions; both enums are #[non_exhaustive], so an exhaustive downstream match already required a wildcard arm. ToSocketAddrsWithHostname gained a host_port method with a default body, so external implementors compile unchanged.
Note one user-visible behavior change that is a fix rather than a break: with no forced address family, forwarding targets are now resolved by the remote server rather than the client (#257). A deployment that relied on client-side resolution of a forwarding target name can force -4 or -6 to restore local resolution.
Known Issues
@cert-authoritylines still fall through to ordinary TOFU by default. bssh has no CA signature validation, so the strict behavior is opt-in throughBSSH_CERT_AUTHORITY_POLICY=rejectrather than the default.- With no determinable known_hosts path,
accept-newpins keys in memory only. The pin does not survive the process, so a first connection in a new process is still an unverified first use. - The cross-process known_hosts lock is advisory and cooperative. It serializes bssh against other bssh processes, not against an unrelated writer editing known_hosts directly.
- SOCKS4 destinations remain IPv4-only by protocol definition. Under forced IPv6 they are now refused rather than silently tunneled, which is the intended behavior but means SOCKS4 and
-6cannot be combined. tests/integration_test.rshas a localhost-auth fixture mismatch: its precheck lets systemsshuse an agent whileParallelExecutor::newdisables agent auth and supplies only~/.ssh/id_rsa, so three localhost cases fail locally in agent-only environments. CI's--skip integration_testdoes not exclude the test binary.
What's Changed
- fix: close host key fail-open paths by @inureyes in #259
- chore: resolve host key verification follow-ups by @inureyes in #260
- fix: preserve forwarded target hostnames by default by @inureyes in #258
- fix: repair merged host-port integration by @inureyes in #262
- fix: honor address family for SOCKS4 forwarding by @inureyes in #263
- fix: support SOCKS5 IPv6 literal destinations by @inureyes in #261
Full Changelog: v2.4.0...v2.4.1
v2.4.0
Host key verification in the default accept-new mode now actually verifies. Address family selection (-4/-6, ssh_config AddressFamily) works across every connection path. bssh ping reports a real exit code.
New Features
- Address family selection works end to end.
-4/-6and the ssh_configAddressFamilykeyword now govern direct connections forexec, interactive sessions,ping, and SFTPupload/download, plus port forwarding listeners and targets and every hop of a-Jchain (#246, #248). Precedence follows OpenSSH: command line flag, then config keyword, then theanydefault. - Bracketed IPv6 address literals are accepted in host specifications:
[::1],[::1]:22,user@[::1], anduser@[::1]:22, in-H, SSH-style destinations, and both simple and detailed cluster node entries (#251). bssh pingfollows a 0/1/255 exit code contract, so health checks can branch on it (#245). 0 means every targeted host connected and authenticated, 1 means at least one succeeded and at least one failed, and 255 means no host succeeded or bssh failed before attempting a connection.
Improvements
- ssh_config connection settings resolve against the actual target host instead of once per dispatch, so a per-host
Hostblock applies toexec,ping,upload, anddownloadrather than only to SSH mode (#249). This coversAddressFamily,Compression,ServerAliveInterval, andServerAliveCountMax. A jump hop now resolves against its ownHost bastionblock instead of inheriting the destination's settings. - Interactive connection failures print the full anyhow context chain, so a jump-host failure names the hop that failed instead of collapsing to a single outer message (#238).
- Forcing an address family that has no resolved address fails with
no IPv6 address found for <host>(or the IPv4 equivalent) instead of the genericcould not resolve to any addresses(#246). - The man page describes only current behavior. Two NOTES subsections that were release history moved to or were already covered by the changelog.
Bug Fixes
bssh pingalways exited 0 while its help text promised otherwise, so every script branching on it passed unconditionally (#245).-4/-6and ssh_configAddressFamilywere advertised in--helpand the man page, parsed, and then discarded, sobssh -6against a dual-stack host could still connect over IPv4 (#246).- Jump hops past the first tunneled over the wrong family. Those hops resolve their target locally and send a literal IP in the
direct-tcpiprequest, so a-6invocation could reach IPv4 for every hop after the first (#248). - ssh_config lookups outside SSH mode queried the literal
"*"and missed per-host blocks, sobssh -H v6node uptimeignored aHost v6nodestanza (#249). - Bracketed IPv6 literals died in the hostlist expander before any connection was attempted, because
[and]are its range expression delimiters (#251). A bare::1reached the connect path and failed resolution instead. - The intermediate jump-hop error interpolated the jump host twice, printing
Failed to connect to jump host bastion (hop 2): bastion. - The direct-connect and file-transfer paths built their anyhow chain backwards, so the raw SSH error rendered above the friendly message as a near-duplicate.
- A context message that restated its own cause printed the same text twice on one line, such as
Password authentication failed.: Password authentication failed. - Fixed the
occuredmisspelling and theSshcapitalization in theSshErrorandSftpErrormessages.
CI/CD Improvements
None
Technical Details
Four defects in accept-new and strict host key verification are fixed (#239):
accept-new, the documented default, mapped toNoCheck. Any server key was accepted unconditionally, nothing was written toknown_hosts, and changed keys were never rejected. It now checks the offered key, records unknown hosts with an OpenSSH-style "Permanently added" notice, and rejects changed keys with the standard warning banner including the SHA256 fingerprint, offending file and line, and a remediation hint. Strict (yes) mode no longer downgrades toNoCheckwhen the known_hosts file is missing.- A host with two or more keys of one algorithm was rejected as changed as soon as one entry differed, a false man-in-the-middle alarm whose own remediation would have deleted the legitimate pin. Lookup now applies OpenSSH's rule: any recorded key equal to the offered key verifies.
- Hostnames were recorded verbatim with no escaping, so a name containing a newline, space,
#, or comma could pin a key against an unintended host, make a host permanently unconnectable, or hide the entry from later lookups. Names that cannot round-trip are now rejected before anything is looked up or written. @revokedand@cert-authoritymarker lines were silently ignored, so an explicitly revoked key was recorded and accepted like any first use. A dedicated scan now runs first: a matching@revokedline is a hard rejection, and a@cert-authoritymatch warns. Hostname matching is case-insensitive, and~/.sshandknown_hostsare created with mode 0700/0600 directly rather than under the process umask.
SshConnectionConfigResolver is the new seam for per-target settings, combining command line overrides, YAML defaults, and ssh_config Host blocks. It runs at the ParallelExecutor per-node boundary and is threaded through SshClient and JumpHostChain.
The address family filter for tunneled targets reuses open_direct_tcpip_channel_with_family, the same mechanism already used for -L forwarding targets.
Dependencies
No Cargo.toml requirement changed. Locked versions moved: russh 0.62.1 to 0.62.5, tokio 1.52.3 to 1.53.1, clap 4.6.1 to 4.6.5, serde 1.0.228 to 1.0.229, and ratatui 0.30.0 to 0.30.2. The transitive set shifted with them, adding ratatui-termina, termina, palette, sponge-cursor, and approx and dropping scc, sdd, prettyplease, and the wit-bindgen / wasm-encoder / wasmparser toolchain crates.
Breaking Changes
Behavior:
bssh pingagainst hosts that are all unreachable now exits 255. It previously exited 0, which is also not the 1 the old help text implied.accept-newnow performs real verification. A changed host key is rejected where it was previously accepted, and connections that silently succeeded against a rotated or substituted key will now fail until the entry is reconciled.- With
-6, a-Lor-Dspecification that does not name a bind address listens on::1instead of127.0.0.1, and the*:portwildcard form listens on::instead of0.0.0.0. Scripts that pass-6and assume an IPv4 loopback listener should name the bind address explicitly, for example-L 127.0.0.1:8080:example.com:80. - An unbracketed IPv6 literal is rejected with
IPv6 address literals must be enclosed in brackets, for example '[::1]'instead of failing during resolution.
Library API, for consumers of the bssh crate:
bssh::commands::ping::ping_nodesreturnsResult<PingOutcome>instead ofResult<()>(#245). Callers that ignored the oldOk(())keep compiling; callers that matched on it exhaustively update the pattern.- The four
SshClient::*_with_jump_hostshelpers take a&SshConnectionConfigand anOption<&SshConnectionConfigResolver>in place of their former trailing argument (#246, #249). ForwardingSpec::parse_local,parse_dynamic, andparsetake anAddressFamilyargument (#246).ForwardingConfiggained a publicaddress_familyfield,bssh::ssh::tokio_client::Errorgained aNoAddressForFamilyvariant, andbssh::ssh::tokio_clientexports the newSshConnectionConfigResolver(#246, #249).
Known Issues
@cert-authoritylines warn but do not gate the connection. bssh has no CA signature validation, so a match falls through to ordinary TOFU rather than failing closed, which would break every working CA setup with no workaround.- When no home directory can be determined,
accept-newfalls back to no verification after printing a warning to stderr, since there is no persistent trust state to record against. - The
known_hostsfirst-write lock is process-wide. Two concurrent bssh processes recording the same new host are not serialized against each other. - SOCKS4 requests carry a literal IPv4 destination by protocol definition and are passed through unfiltered by the address family preference. Forwarding target filtering for
-Land SOCKS5 is a best-effort hint, since the remote server performs the actual connect.
What's Changed
- fix: show full anyhow error chain on interactive connect failure by @inureyes in #240
- fix(security): implement real TOFU for accept-new host key mode by @inureyes in #241
- fix: wire up the -4/-6 address family flags and AddressFamily by @inureyes in #247
- fix: report a real exit code from bssh ping by @inureyes in #250
- feat: filter tunneled jump targets by address family by @inureyes in #252
- fix: resolve ssh config per connection target by @inureyes in #254
- fix: accept bracketed IPv6 host literals (#251) by @inureyes in #253
Full Changelog: v2.3.1...v2.4.0
v2.3.1
New Features
- Preserved Kitty keyboard flags, xterm
modifyOtherKeys, and the selected terminal screen when bssh runs inside an outer TUI. (#237)
Improvements
- Raised the workspace MSRV and Debian/Launchpad build toolchain from Rust 1.93 to 1.96. (#231, #232)
- Removed stale vendored-russh attribution and kept the README release summary bounded. (#230)
- Fixed the file-transfer filter example so it compiled as a doctest.
Bug Fixes
- Restored ordinary keyboard input after a remote PTY disconnect left Kitty keyboard or xterm
modifyOtherKeysmodes enabled. (#235)
CI/CD Improvements
- Matched Homebrew formula URLs and checksums by artifact, rejected failed or malformed downloads, and validated formula content before committing tap updates. (#233)
Technical Details
- Queried both terminal screens before starting the input reader, forwarded non-reply input, tracked delivered DEC 1049 transitions, and shared one idempotent cleanup path across normal and forced teardown. (#235, #237)
Dependencies
None
Breaking Changes
None
Known Issues
None
What's Changed
- docs: tidy README Recent Updates and drop stale bssh-russh from NOTICE by @inureyes in #230
- build: bump Launchpad PPA toolchain and MSRV to Rust 1.96 by @inureyes in #231
- build: source resolute toolchain from rustc-release PPA and pin vendor_rust to 1.96.0 by @inureyes in #232
- fix: harden tap bump against layout changes and bad downloads by @inureyes in #233
- fix: restore enhanced keyboard modes on PTY teardown by @inureyes in #235
- feat: preserve outer TUI keyboard state by @inureyes in #237
Full Changelog: v2.3.0...v2.3.1
v2.3.0
New Features
- Server-side SSH compression is now configurable via
server.compression(YAML),BSSH_COMPRESSION(env), andServerConfigBuilder::compression, replacing the hard-disabled behavior from v2.2.x. The default stays off; enabling it logs a warning about the russh delayed-zlib desync (#220).
Improvements
- Roughly double single-connection SFTP write throughput on
bssh-server.build_russh_confignow advertises a 65535-byte maximum packet size and an 8 MiB window (both configurable viaserver.maximum_packet_size/server.window_sizeandBSSH_MAX_PACKET_SIZE/BSSH_WINDOW_SIZE), the server SFTP loop pipelines writes with sequential coalescing, and sequential handlers elide the per-chunk seek. Local loopback 1 GiB benchmark: upload 71 to 437 MiB/s (6.2x), download 73 to 282 MiB/s (3.9x) (#187, #224). - Set
TCP_NODELAYon accepted server sockets, removing a ~37 ms delayed-ACK stall per strictly sequential SFTP round trip (#227). - Wire the ssh_config
Compression yes|nodirective into the russh client; it was parsed but silently ignored before.Compression yesadvertises eagerzlib, unset/noadvertises onlynone, andzlib@openssh.comis never advertised client-side (#219).
Bug Fixes
- Fix an SFTP session deadlock under paramiko's unbounded READ prefetch that froze every
bssh-serverdownload to a paramiko client at its initial 2 MiB channel window. The SFTP intake queue is now byte-bounded (max_buffered_request_bytes, default 8 MiB) instead of count-bounded, so the reader never stops draining the channel. Client-initiated disconnects are no longer logged at ERROR (#227). - Make
sftp.root/scp.rootchroot usable by re-anchoring client paths under the root;cd,get,open, andstatpreviously failed with "path outside root" and only bare/and readdir worked. SCP is unified with thesftp.rootbehavior (#214). - Confine absolute SFTP symlink targets to the chroot so a chrooted client can no longer create a link that resolves to the host filesystem (#214).
- Advertise only
noneSSH compression by default so clients that negotiatezlib@openssh.com(Cyberduck,sftp -C) no longer complete the handshake and then drop mid-session withSshEncoding: length invalid(#215).
CI/CD Improvements
- Add a CI MSRV job that reads
rust-versionfromCargo.tomland runscargo check --workspace --lockedon that toolchain, so the declared MSRV can no longer silently drift from what actually builds.
Technical Details
- Server SFTP write path: the strict read-process-write-flush loop is restructured into a reader task with a byte-bounded intake queue plus an in-order processor. Responses flush once per burst instead of once per request, and consecutive sequential
SSH_FXP_WRITErequests to the same handle are coalesced (max_write_coalesce_len, default 256 KiB) while every merged request id still receives its own status reply. - Tuned
[profile.release](lto = "fat",codegen-units = 1) with documentedRUSTFLAGS="-C target-cpu=..."guidance for self-builds. - New
tools/bench/harness:bench.sh(bssh-server vs OpenSSH SFTP throughput, with optional before/after and single-core pinning),profile.sh(perf plus inferno flamegraph of the upload path), andinterop/(sshj and paramiko round-trip checks). Loopback-only and byte-for-byte verified (#228). - Unify the auxiliary binaries on the "Broadcast SSH" name in
--help/version output, and add an Apache-2.0NOTICEfile (#210).
Dependencies
- Drop the vendored
bssh-russhfork and build against crates.iorussh0.62.1 now that both fork patches are upstream (PTYHandle::datafix in russh 0.62.0, SHA-1 MAC exclusion in 0.60.2).ssh-keyis bumped to=0.7.0-rc.11to match russh 0.62.x, andchannel_open_sessionis adapted to the new 0.62 handler signature (#212).
Breaking Changes
- The workspace minimum supported Rust version is raised from 1.88 to 1.93. The old 1.88 floor was already inaccurate (
rustyline18 needsFile::lock, stabilized in Rust 1.89); 1.93 aligns with the Launchpad PPA and Ubuntu 26.04 toolchains. Building from source now requires rustc 1.93 or newer.
Known Issues
- Server-side SSH compression stays off by default because russh's delayed-zlib (
zlib@openssh.com) transport desyncs a few packets after compression activates post-auth (reproduced on russh 0.61.1 and 0.62.1). Enablingserver.compressionadvertiseszlib/zlib@openssh.comand logs a warning; the underlying desync is left to be fixed upstream. - Native Windows client execution is not supported. Linux and macOS are fully supported; WSL2 is the recommended path on Windows (see #213).
What's Changed
- build: standardize Rust toolchain on 1.93 (PPA build, MSRV, CI verification) by @inureyes in #209
- docs: add Apache-2.0 NOTICE and align tool naming to Broadcast SSH by @inureyes in #210
- chore(deps): drop bssh-russh fork, use upstream russh 0.62.1 by @Yaminyam in #212
- fix(sftp): resolve chroot client absolute paths relative to the root (fixes #214) by @Yaminyam in #218
- fix(server): offer only
noneSSH compression to avoid zlib@openssh.com desync (fixes #215) by @Yaminyam in #217 - docs: state native Windows client support is not supported by @inureyes in #221
- feat(server): make SSH compression configurable instead of hard-disabled by @inureyes in #222
- fix: wire ssh_config Compression directive into the russh client by @inureyes in #223
- perf(sftp): raise channel sizing, pipeline server writes, tune release build by @inureyes in #224
- feat(tools): add SFTP benchmark, flamegraph, and interop harness by @inureyes in #228
- fix(sftp): fix paramiko prefetch deadlock and enable TCP_NODELAY by @inureyes in #229
Full Changelog: v2.2.3...v2.3.0
v2.2.3
bssh v2.2.3
A dependency-modernization and security release: both internal russh forks are synced to their latest upstream releases (bringing the current RustCrypto generation), ssh-key is unified onto a single version, and a transitive time advisory is patched.
Minimum supported Rust is now 1.88 (raised from 1.85), required by the
time0.3.47 security fix below. This affects building from source only; prebuilt binaries, Homebrew, and Debian packages are unaffected.
Security
- RUSTSEC-2026-0009 (medium, 6.8): a stack-exhaustion denial of service in
time0.3.45, pulled transitively viaratatui0.30. Fixed by bumpingtimeto 0.3.47 (plusnum-convandtime-core);cargo auditnow reports 0 vulnerabilities. Becausetime0.3.47 requires Rust 1.88, the workspace MSRV is raised to 1.88, which also keeps the MSRV-aware resolver from revertingtimeto the vulnerable 0.3.45 (#208).
New Features
None
Improvements
- SFTP server error replies now carry a human-readable message (via russh-sftp 2.3.0's
StatusReply), not just a numeric status code (#207).
Bug Fixes
None
CI/CD Improvements
- Bump
action-gh-releaseto v3 for the Node 24 runtime. - Adjust the download shield badge.
Technical Details
bssh-russhsynced to upstream russh 0.61.1 (from a 0.60.3 base), adopting the new RustCrypto generation: sha2/sha1 0.11, hmac 0.13, aes 0.9, cbc 0.2, ctr 0.10, digest 0.11, pbkdf2 0.13, ssh-key 0.7.0-rc.10, ssh-encoding 0.3.0-rc.9. The high-frequency PTYHandle::data()drain fix is re-ported onto the new server session loop (still absent upstream in 0.61.1, so the fork stays necessary), three now-upstreamed patches are removed, and a PTY regression test is added (#207).bssh-russh-sftpsynced to upstream russh-sftp 2.3.0 (from 2.1.2), with the pipelined File I/O helpers re-applied and the SFTP server adapted to the newInto<StatusReply>handler API (#207).- Fork maintenance tooling is now self-contained (
create-patch.shclones upstream;sync-upstream.shapplies allpatches/*.patch), and both fork READMEs were rewritten (#207). - Minimum supported Rust raised from 1.85 to 1.88 (declared via
rust-versionon the root crate andbssh-russh), required bytime0.3.47.
Dependencies
- Internal forks:
bssh-russh0.60.3 to 0.61.1 andbssh-russh-sftp2.1.2 to 2.3.0, both published to crates.io. ssh-keyunified to a single=0.7.0-rc.10;argon2gains itsstdfeature.time0.3.45 to 0.3.47 (security, see above).
Breaking Changes
- Minimum supported Rust is now 1.88 (was 1.85). This affects building bssh from source only; prebuilt release binaries, Homebrew, and Debian packages are unaffected.
Known Issues
None
What's Changed
- update: sync russh/russh-sftp forks to upstream and unify ssh-key by @inureyes in #207
- fix: bump time to 0.3.47 for RUSTSEC-2026-0009 by @inureyes in #208
Full Changelog: v2.2.2...v2.2.3
v2.2.2
This is a focused bug-fix release that stops bssh from dropping idle but healthy SSH sessions (#206). It is a drop-in upgrade from v2.2.1 with no configuration changes required.
New Features
None
Improvements
None
Bug Fixes
- Keep idle SSH sessions alive (#206). Three related changes now work together so an open SSH session is no longer closed while it is still healthy:
- Lower the default keepalive interval from 60s to 30s. The default
--server-alive-intervalnow sits below common one-minute idle reapers (load balancers, NAT gateways, and sshd's ownClientAliveInterval), so bssh sends keepalive traffic before any of them decide the connection is idle. - Normalize
--server-alive-interval 0to fully disabled keepalive. Passing0previously constructed a zero-duration russh timer.Some(0)is now treated exactly likeNonein both the russh client config and the TCPSO_KEEPALIVEpath. - Never close idle interactive sessions locally. The client-side
inactivity_timeoutis now disabled unconditionally, so a healthy session that legitimately produces no inbound data for a long time (tmux, an idle shell, a long-running REPL) is never torn down by bssh itself. When keepalive is enabled, russh's keepalive counter is the sole dead-peer detector.
- Lower the default keepalive interval from 60s to 30s. The default
CI/CD Improvements
None
Technical Details
- Dead-peer detection now resolves in about 120s (three unanswered 30s probes plus the next timer tick that observes them) rather than the previous 180s.
- Changes are confined to
src/cli/bssh.rs,src/config/types.rs, andsrc/ssh/tokio_client/connection.rs, with expanded coverage intests/ssh_keepalive_test.rs.
Dependencies
None
Breaking Changes
None. The default keepalive interval changed from 60s to 30s, but this only increases keepalive frequency and stays fully overridable via the --server-alive-interval flag and the server_alive_interval config field.
Known Issues
None
Full Changelog: v2.2.1...v2.2.2
v2.2.1
New Features
None.
Improvements
- Bump workspace dependencies and sync both internal russh forks to upstream stable (#203). Main bssh:
lru0.17 to 0.18 (lifetime fix inget_or_insert_mut_ref),signal-hook0.3 to 0.4 (only the unusedlow_level::pipeAPI changed),opentelemetry/opentelemetry_sdk/opentelemetry-otlp0.31 to 0.32, plusnix0.31.2 to 0.31.3 and transitivepin-project/tower-http/zerofrompatches viacargo update. - Sync
bssh-russh0.60.1 to 0.60.3 (upstream stable). Picks upaws-lc-rs1.16.3 to 1.17.0 and the upstream v0.60.2 unreleased fixes our previous PR #193 forward-port had already brought in (#690 SHA-1 MAC exclusion, #693 channel write ordering). - Sync
bssh-russh-sftp2.1.1 to 2.1.2 (upstream stable). The fork's originalserde_bytesperf fix was absorbed by upstream 2.1.2 and is moved topatches/historical/for provenance; the remaining custom value-add is the two pipelined File I/O helpers (write_all_pipelined/read_to_writer_pipelined), re-ported on top of upstream 2.1.2's newFeaturesAPI with chunk sizing derived fromfeatures.limits.{write,read}_lenorfeatures.max_packet_len.saturating_sub({WRITE,READ}_OVERHEAD_LENGTH)instead of the removedMAX_*_LENGTHconstants.crates/bssh-russh-sftp/Cargo.tomlswapsflurryfordashmap6.1.0 and addsserde_bytesas a direct dep to match upstream's set.
Bug Fixes
- Add the missing
[dev-dependencies]block to thebssh-russhfork so its inline test target actually compiles for the first time since the fork's inception (#204). The fork'ssrc/client/test.rs,src/keys/mod.rs, andsrc/tests.rswere imported verbatim from upstream russh during the initial sync (commit508aa3f0), but the matching[dev-dependencies]were never copied across, socargo test -p bssh-russhhad failed with E0433 onenv_logger/tempfileand cascading E0282 type-inference errors. Adds a minimal block withenv_logger,tempfile, andtokiowithprocess/macrosfeatures (additive merge). 75 tests now run, covering agent client/server round-trip, PKCS#8 / OpenSSH key decoding, channel lifecycle, GEX, compression, future certificate auth, and server kex junk handling. The agent tests directly exercise the new frame-length cap from PR #203 (the CVE-2026-46673 mitigation) by spawning a realssh-agentover a Unix-domain socket. Workspace test aggregate climbs from 1796 to 1871 passed. - Drop a redundant
.into_iter()on aIterator::chainargument in the synced SFTP session loop (crates/bssh-russh-sftp/src/client/session.rs:194) to satisfy rustc 1.95's stricterclippy::useless_conversionlint (#205). The line was imported verbatim from upstream russh-sftp 2.1.2 in PR #203 and broke CI after the toolchain bump. PR #203 was developed on rustc 1.93.1 where the case did not lint-fire.
CI/CD Improvements
None.
Technical Details
The agent frame-length cap forward-port (CVE-2026-46673 SSH-agent half) lives in crates/bssh-russh/src/keys/agent/{client,server}.rs. Both files now declare const MAX_AGENT_FRAME_LEN: usize = 256 * 1024; and route the receive path through a new read_frame() helper that reads the 4-byte big-endian length prefix, rejects values above the cap with Error::AgentProtocolError before resizing the receive buffer, then reads exactly len bytes. Mirror of upstream russh commit a2d48a7 by Mika Cohen. Recorded as crates/bssh-russh/patches/agent-frame-length-cap.patch so sync-upstream.sh's reverse-apply dry-run will auto-skip on the next sync.
The pipelined SFTP File I/O port lives in crates/bssh-russh-sftp/src/client/fs/file.rs. The original constants (MAX_READ_LENGTH = 261120, MAX_WRITE_LENGTH = 261120) and the bounded_chunk_size(limit, default) helper are gone; chunk size is now derived per-call from self.features.limits.{write,read}_len (when negotiated) or self.features.max_packet_len.saturating_sub(WRITE_OVERHEAD_LENGTH + handle.len() as u32) as u64 (write side) / self.features.max_packet_len.saturating_sub(READ_OVERHEAD_LENGTH) as u64 (read side). The high-level pipelining loops (FuturesUnordered for in-flight requests, BTreeMap reorder buffer for reads, file_end size-cap for EOF detection) are unchanged. The fork's [dev-dependencies] adds futures directly because upstream 2.1.2 no longer depends on it transitively for this code path.
Dependencies
lru0.17.0 to 0.18.0 (lifetime fix inget_or_insert_mut_ref; no API surface change for ourLruCache<PathBuf, CacheEntry>usage insrc/ssh/config_cache/manager.rs).signal-hook0.3 to 0.4 (onlylow_level::pipeAPI changed:IntoRawFdtoOwnedFd. Ourconsts::SIGWINCHanditerator::Signalsusages insrc/pty/are stable across the bump).opentelemetry/opentelemetry_sdk/opentelemetry-otlp0.31 to 0.32 (the removedExportConfig/HasExportConfig/with_export_config()trio isn't called from oursrc/server/audit/otel.rs;WithExportConfig::with_endpoint,LogExporter::builder().with_tonic(),SdkLoggerProvider::builder(),Resource::builder(), and theLogRecordAPI all remain source-compatible). Note: 0.32 now errors when anhttps://endpoint is configured without a TLS feature; our current features stay at[grpc-tonic, logs]because all uses today arehttp://, but a follow-up will be needed if anyone runs the audit exporter against TLS in production.nix0.31.2 to 0.31.3 plus transitive patches inpin-project1.1.12 to 1.1.13,tower-http0.6.10 to 0.6.11,zerofrom0.1.7 to 0.1.8 viacargo update.aws-lc-rs1.16.3 to 1.17.0 (also pullsaws-lc-sys0.40.0 to 0.41.0).russh-cryptovec0.59.0 to 0.60.3 inside thebssh-russhfork. Brings in the cryptovec hardening half of CVE-2026-46673.bssh-russh-sftpfork: replaceflurry0.5 withdashmap6.1.0 to match upstream russh-sftp 2.1.2; addserde_bytes0.11 as a direct dep (no longer transitive via removed code paths).
Breaking Changes
None for users of the bssh CLI or bssh-server daemon. The package alias for the SFTP crate stays russh-sftp so all use russh_sftp::* import paths continue to work unchanged. Within the vendored bssh-russh-sftp crate, the upstream 2.1.2 Extensions to Features rename and the flurry to dashmap migration would matter to anyone depending on the fork directly, but no external consumer does today (the fork is workspace-internal).
Known Issues
- The remaining "outdated" direct deps in
bssh-russh(aes0.9,hmac0.13,sha1/sha20.11,digest0.11,cbc/ctr/desnew majors,pkcs5/pkcs8stable, and the RC familyecdsarc.18 /rsarc.18 /elliptic-curverc.32 /ed25519-dalekpre.7) are blocked by RustCrypto's coordinated release model: shared trait crates, and the pre-release crypto crates we depend on still require the older trait versions. Upstream russh stable v0.60.3 carries the same constraints, so we deliberately stay aligned with it. - Transitive
lru@0.16.4andsignal-hook@0.3.18are pulled in byratatui-coreandcrosstermrespectively and will resolve themselves whenever those upstream crates bump. opentelemetry-otlp0.32 errors when anhttps://endpoint is configured without an explicittls-ringortls-aws-lcfeature. We did not enable a TLS feature in this release because all current uses arehttp://; users running the audit exporter against a TLS-protected OTLP collector will need to either disable the warning by switching tohttp://or to enable a TLS feature in a follow-up build.
What's Changed
- update: bump workspace deps and sync russh forks to upstream stable by @inureyes in #203
- fix: add missing dev-dependencies to bssh-russh fork by @inureyes in #204
- fix: drop useless .into_iter() on chain arg in synced sftp session by @inureyes in #205
Full Changelog: v2.2.0...v2.2.1
v2.2.0
v2.2.0 — Single-prompt password collection, dependency cleanup
This release reworks --password collection to mirror the existing Arc<SudoPassword> pattern (prompt once in the dispatcher, share an Arc<Password> across all parallel tasks), making -S consistent across subcommands, and continues the dependency-hygiene work started in v2.1.4 by dropping five stale or redundant direct dependencies and clearing all cargo-audit findings.
New Features
BSSH_PASSWORDenvironment variable for non-interactive password authentication (#201). Documented in the man page and the README environment-variables section. Discouraged for production deployments; recommended for automated test pipelines and CI scenarios where SSH agent or key-based auth is not feasible.
Improvements
- Collect
--passwordonce up-front in the dispatcher and share the secret across all parallel SSH tasks viaArc<Password>(backed bysecrecy::SecretString, auto-zeroized on drop) (#201, closes #200). The prompt now runs before any executor or indicatifMultiProgressis initialized, so the terminal is in a clean state when reading the password. The pre-collected secret is threaded throughParallelExecutor,ExecutionConfig,ConnectionConfig,FileTransferParams, the jump-hostdetermine_auth_methodpath, the download glob-resolution path (SshClient::connect_and_execute_with_host_check), the SFTP*_with_jump_hostshelpers, and the legacyexecute_command_with_forwardingpath. The in-taskrpassword::prompt_password()call remains only as the fallback for the OpenSSH-style "all key methods failed" opportunistic prompt path, which has no dispatcher-side collection. - Warn on stderr when
-S/--sudo-passwordis passed to subcommands where it has no effect (ping,upload,download,list,cache-stats, and interactive shells) (#201, follow-up #200). The warning routes througheprintln!sobssh ... ping | grep ...pipelines stay clean.execand the SSH-mode interactive path continue to honor-Sas before. - Tighten sudo-password collection so it only applies to exec paths that can inject sudo responses, and avoid unused SSH password collection for local-only dispatcher paths.
Bug Fixes
- Fix
--passwordrace across parallel SSH connections (#200, #201). Multiple per-node tasks previously raced for stdin: the prompt could be missed, repeated per node, or interleaved with the indicatif progress UI rendering. - Fix
-Sbeing silently dropped byping,upload, anddownload(#201, closes #200). The dispatcher only readcli.sudo_passwordin theexecand interactive branches, so users could pass-Sto other subcommands without any error or feedback that the flag had no effect. - Fix the
-Signored-warning landing on stdout (#201, follow-up). The previoustracing::warn!was being mixed into stdout under the default tracing subscriber, breaking shell pipelines that consumed bssh output.
CI/CD Improvements
None.
Technical Details
- New
src/security/password.rsmodule mirroring the existingsrc/security/sudo.rsdesign: aPasswordwrapper backed bysecrecy::SecretStringwith auto-zeroize on drop,prompt_password()with a non-host-specific prompt,get_password_from_env()readingBSSH_PASSWORD, andget_password(warn_env)combining both. Wrapped inArc<Password>for sharing across per-node tasks without duplicating secret material. AuthContext(src/ssh/auth.rs) gains apassword: Option<Arc<Password>>field and awith_pre_collected_password()builder.password_auth()consumes the pre-collected value when available.- Migrated four call sites from
lazy_static!/once_cell::sync::Lazy/OnceCelltostd::sync::LazyLock/OnceLock:ui/tui/progress.rs,executor/output_sync.rs,pty/terminal.rs,utils/logging.rs,ssh/ssh_config/env_cache/global.rs,ssh/config_cache/global.rs(edition 2024 mandates Rust 1.85+). - Migrated three forwarding reconnect-jitter sites from
fastrand::u64(range)torand::random_range(range);rand0.10 was already a direct dependency. - Migrated five callers of
std::io::stdin().is_terminal()/stdout().is_terminal()from the unmaintainedattycrate. - 9 new unit tests for
Password(creation, empty rejection, debug redaction, clone independence, Arc sharing, env var handling, dispatcher-collection-pattern verification) and 2 new tests inssh/auth.rsconfirming the builder semantics and thatpassword_auth()never blocks on stdin when a pre-collected value is present.
Dependencies
- Drop five stale or redundant direct dependencies (#199):
arrayvec(unused),ctrlc(replaced withtokio::signal::ctrl_c),directories(replaced withdirs, already used in 16 other sites),signal-hook0.4.4 (downgraded to 0.3 to share the cratecrosstermalready pulls in viasignal-hook-mio), plus the macOSobjc2/block2/dispatch2chain pulled in byctrlc. Three more crates (lazy_static,once_cell,fastrand) remain transitively but are no longer directly referenced.Cargo.lockloses 76 lines including the entiresignal-hook0.4.4 subtree. - Replace
attywithstd::io::IsTerminal(#198): drops RUSTSEC-2024-0375 (unmaintained) and RUSTSEC-2021-0145 (unsound unaligned read). - 33 transitive patch bumps from
cargo updatewithin current semver constraints (#198): tokio 1.52.1 to 1.52.3, rustls 0.23.39 to 0.23.40, h2 0.4.13 to 0.4.14, digest 0.11.2 to 0.11.3, rpassword 7.4.0 to 7.5.2, and others.
Breaking Changes
None. BSSH_PASSWORD and the -S warning are both additive: existing scripts that happen to pass -S to non-applicable subcommands keep working but now receive visible stderr feedback.
Known Issues
- RUSTSEC-2023-0071 (RSA Marvin Attack) acknowledged via
.cargo/audit.tomlignore with an explanatory comment (#198). Bothrsa0.9.10 (viassh-key0.6.x) andrsa0.10.0-rc.17 (via the vendoredbssh-russhfork) are affected, and no fixed upstreamrsarelease exists. Bumping to 0.10.0-rc.18 conflicts with thebssh-russhpkcs5 = "=0.8.0-rc.13"pin. Users handling untrusted hosts should prefer Ed25519 or ECDSA keys instead of RSA.
What's Changed
- update: clean cargo-audit by removing atty and pinning rsa advisory by @inureyes in #198
- refactor: drop stale and redundant dependencies by @inureyes in #199
- fix: collect --password once up-front and honor -S consistently across subcommands by @inureyes in #201
- fix: warn when sudo password is ignored outside exec by @inureyes in #202
Full Changelog: v2.1.4...v2.2.0
v2.1.4
SFTP transfer performance release. Streams uploads/downloads in 255 KiB chunks instead of buffering whole files, pipelines up to 64 concurrent SFTP requests, and raises the server-side MAX_READ_SIZE to the 255 KiB SFTP standard. On a 1 GiB transfer over loopback this drops upload peak RSS from ~3.23 GB to ~20 MB and wall time from 38.6 s to 3.5 s; multi-GB transfers no longer OOM the client.
New Features
None.
Improvements
-
Stream SFTP uploads/downloads instead of buffering whole files in memory (#195).
upload_file/upload_dir_recursivepreviously loaded the entire local file into aVec<u8>viatokio::fs::readbefore callingwrite_all, anddownload_file/download_dir_recursivecalledread_to_endinto a pooled buffer plus aclone()to a separateVecbefore writing locally — so multi-GB transfers had peak RSS that scaled with file size and large files OOM'd the client. Each path now uses astream_copy()helper looping on 255 KiB reads/writes through the existingAsyncRead/AsyncWriteimpls ontokio::fs::Fileandrussh_sftp::client::fs::File. Buffer size matchesMAX_WRITE_LENGTHso each chunk maps to one SFTP packet without further fragmentation.Verified locally on macOS arm64 against
bssh-serverv2.1.3 over loopback with a 1 GiB file:Op Build real RSS upload unpatched 38.65s 3.23 GB upload streaming 3.47s 20 MB download unpatched 3.93s 2.17 GB download streaming 3.41s 16 MB -
Pipeline up to 64 concurrent SFTP requests for upload and download (#196). Bounded pipelined SFTP upload/download helpers replace the previous strictly-sequential request/response loop. Review follow-ups also: cap server-advertised read/write lengths against local maxima to avoid oversized allocations from untrusted SFTP metadata, bound the download reorder queue across both in-flight and pending out-of-order responses, use
fstatsize info where available to avoid reads past EOF and validate unexpected short reads, and preserve remote download handle shutdown after syncing with main. -
Raise bssh-server SFTP
MAX_READ_SIZEfrom 64 KiB to the 255 KiB SFTP standard (#197). The server previously hard-capped every SFTPREADreply at 64 KiB regardless of what the client requested, whilebssh-russh-sftpand OpenSSH'ssftp-serverboth useMAX_READ_LENGTH = 261120(255 KiB). A client asking for a 256 KiB chunk only ever got 64 KiB back, forcing four extra requests per byte stream. Bumped to 261120 so server replies match the chunk size used by the rest of the stack — combined with client-side pipelining (#196), this cuts the per-MiB request count on downloads from 16 to 4. Memory exposure stays bounded: handles are still capped atMAX_HANDLES = 1000per session and each in-flight read still uses a single per-request buffer of this size.
Bug Fixes
None.
CI/CD Improvements
None.
Technical Details
- Streaming buffer size (255 KiB) is aligned with the
russh-sftppacket limit so each chunk stays within one SFTP read/write request without further fragmentation. - Downloaded remote handles are now explicitly closed after successful copies; chunk-size capping and in-memory pipelined upload/download behavior are covered by new SFTP crate tests.
Dependencies
None — Cargo.lock only changed because the workspace package bssh itself bumped from 2.1.3 to 2.1.4.
Breaking Changes
None.
Known Issues
None at release time. The 64-way SFTP pipelining is a fixed concurrency cap (not user-configurable in this release); workloads that need a different parallelism point should track follow-up issues on the repository.
What's Changed
- perf: stream SFTP uploads/downloads instead of buffering whole file by @Yaminyam in #195
- perf: pipeline SFTP requests for upload/download (~2-3x speedup) by @Yaminyam in #196
- perf: raise server MAX_READ_SIZE to SFTP standard 255 KiB by @Yaminyam in #197
Full Changelog: v2.1.3...v2.1.4
v2.1.3
bssh v2.1.3
This release fixes several SCP/SFTP path-resolution bugs in bssh-server (issue #186), vendors russh-sftp with a serde_bytes performance optimization (+29% upload throughput), forward-ports unreleased upstream russh fixes, and standardizes man page trailers.
New Features
scp.rootconfiguration field (#186): SCP transfers now honor a chroot setting separate from SFTP. When unset, SCP falls back tosftp.root, so a single top-level chroot setting governs both subsystems unless an admin explicitly wants them split.- Internal fork of
russh-sftpasbssh-russh-sftp(#188) with aserde_bytesperformance fix forSSH_FXP_WRITE/SSH_FXP_DATApackets. The upstream serde derive routedVec<u8>throughdeserialize_seq(byte-by-byte), accounting for ~42% of server CPU during 1 GiB SFTP uploads inperfprofiling. Annotating thedatafields with#[serde(with = "serde_bytes")]and implementing wire-compatibleserialize_byteson the SFTPSerializerroutes through the existing bulkdeserialize_byte_buf/try_get_bytespath. Measured impact on a CPU-bound host (Xeon Silver 4214): 1 GiB SFTP upload throughput improves from 74.8 MiB/s to 96.4 MiB/s (+29%), closing the gap to OpenSSHsftp-serverfrom ~26% to ~5%.
Improvements
- Default file-transfer behavior is no longer chrooted to the user's home directory (#186). With
sftp.root/scp.rootunset (the default), absolute client paths are honored verbatim and relative paths resolve from the user's home directory, matching OpenSSHsftp-server/scpdefaults. Deployments that intentionally want chroot-at-home-dir must now setsftp.root: <home dir>(or equivalent) explicitly. - Forward-port unreleased upstream russh fixes (#193): exclude SHA-1 MACs from
Preferred::DEFAULT/COMPRESSED(upstream russh #690) and fix channel write ordering whenpending_datais non-empty (upstream russh #693). Refactoredsync-upstream.shto iteratepatches/and reverse-apply--dry-runfirst so already-merged patches are auto-skipped. - Switched the top-level
russh-sftpdependency from crates.iorussh-sftp = "2.1.1"to the vendoredbssh-russh-sftppackage; existinguse russh_sftp::...imports continue to work unchanged.
Bug Fixes
- bssh-server SCP/SFTP path doubling on absolute client paths (#186):
ScpHandler::resolve_pathandSftpHandler::resolve_path_staticpreviously re-rooted every absolute client path under the user's home directory, soscp local user@host:/home/work/file.binwrote to/home/work/home/work/file.binandbssh upload local /abs/remote.binfailed withNo such file. The resolver now treats absolute client paths verbatim when no chroot is configured and rejects out-of-chroot absolute paths withpermission_deniedwhen one is. Path-traversal and symlink-escape protections continue to apply. - SCP single-file destinations no longer have the source filename appended (#186):
ScpHandler::receive_filenow consultstarget_is_directory(parsed from-d/-r) and the filesystem state of the resolved target.scp local.bin user@host:/tmp/dest.binnow writes to/tmp/dest.bininstead of/tmp/dest.bin/local.bin. Directory destinations (/tmp/dir/, existing directory, or-d/-rflag) keep the previous filename-appending behavior. - Configured
sftp.rootis no longer dead code (#186): the handler-construction sites inSshHandlerpreviously hard-codeduser_info.home_diras the chroot root and ignoredconfig.sftp.rootentirely. Settingsftp.rootin the YAML configuration now actually changes the SFTP chroot. The same plumbing now exists forscp.root. - Chroot bypass via intermediate-directory symlink: the chroot resolver previously checked only lexical containment for paths whose final component did not exist (typical for new-file creates and
mkdir). A symlink inside the chroot pointing to a directory outside the chroot would let a client targetchroot/escape/newfileand haveopen(...)/create_dir(...)follow the symlink, writing outside the chroot. BothScpHandler::resolve_pathandSftpHandler::resolve_path_staticnow canonicalize the closest existing ancestor of the target path and verify it stays inside the canonicalized chroot, blocking the parent-symlink escape. Found during PR #194 review.
CI/CD Improvements
- Bump GitHub Actions to Node.js 24-compatible versions to address Node.js 20 deprecation warnings that become errors on 2026-06-02 (#191):
actions/checkoutv4 → v6actions/cachev4 → v5actions/upload-artifactv4 → v7apple-actions/import-codesign-certsv3 → v7
Technical Details
- Path resolver now walks up to the closest existing ancestor of the target path, canonicalizes both that ancestor and the chroot root, and verifies the canonical ancestor stays inside the canonical root. Operator-misconfigured chroots that don't exist on disk fall back to the lexical check.
- Removed an unreachable
starts_withcheck in the relative-path chroot resolver afterParentDirclamping (already guarded byif resolved != root). - Added focused unit tests for
scp.root/sftp.rootprecedence rules and the no-chroot default. - Added
tests/scp_sftp_path_resolution_test.rscovering Backend.AI reproduction scenarios (absolute SCP/SFTP paths, no doubling, no chroot honors absolute paths, chroot rejects out-of-root paths,..clamped, chroot/round-trips throughrealpath) plus symlink-escape blocking under chroot. Six regression tests added for the parent-symlink chroot bypass. - Hardened packet length handling in vendored SFTP with checked u32 conversions; removed an extra byte-buffer copy; added wire-format tests; narrowed lockfile delta; fixed clippy warnings; made Keychain-backed tests skip cleanly when local authorization is unavailable.
Documentation
- Standardize man page trailers across
bssh.1,bssh-keygen.1, andbssh-server.8into a consistent BUGS / AUTHORS / COPYRIGHT / SEE ALSO order (#192). Author attribution, contact email, Apache-2.0 license notice, and project homepage link are now uniform across all three pages. - Document
sftp.rootandscp.rootinbssh-server.8configuration sections, and add intermediate-directory-symlink chroot protection to SECURITY CONSIDERATIONS.
Dependencies
tokio1.52.1,clap4.6.1,tracing0.1.44,lru0.17,uuid1.23.1,tokio-util0.7.18bssh-russh:aws-lc-rs1.16.3,ecdsarc.17,elliptic-curverc.31,p256/p384/p521rc.9,tokio1.52.1- Pinned
pkcs5="=0.8.0-rc.13"becausepkcs80.11.0-rc.11 still calls the rc.13-eraParameters::recommendedAPI; stable 0.8.0 renamed it togenerate_recommendedand breaks the build
Breaking Changes
- Default chroot behavior change (#186): the implicit chroot at the user's home directory is removed. With no
sftp.root/scp.rootset, absolute client paths are honored verbatim (matching OpenSSH defaults). Deployments that relied on the implicit confinement must explicitly setsftp.root: <home dir>(or equivalent) in the server YAML.
Known Issues
None.
Full Changelog: v2.1.2...v2.1.3
What's Changed
- chore: bump GitHub Actions to Node.js 24-compatible versions by @inureyes in #191
- docs: standardize man page trailers with AUTHORS, COPYRIGHT, and SEE ALSO by @inureyes in #192
- update: upgrade deps and forward-port unreleased upstream russh fixes by @inureyes in #193
- feat: vendor russh-sftp with serde_bytes perf fix by @Yaminyam in #188
- fix: SCP/SFTP path resolution and dead chroot config (#186) by @inureyes in #194
New Contributors
Full Changelog: v2.1.2...v2.1.3