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