Skip to content

fix(server): offer only none SSH compression to avoid zlib@openssh.com desync (fixes #215)#217

Open
Yaminyam wants to merge 1 commit into
lablup:mainfrom
Yaminyam:fix/sftp-disable-zlib-openssh
Open

fix(server): offer only none SSH compression to avoid zlib@openssh.com desync (fixes #215)#217
Yaminyam wants to merge 1 commit into
lablup:mainfrom
Yaminyam:fix/sftp-disable-zlib-openssh

Conversation

@Yaminyam

@Yaminyam Yaminyam commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Clients that negotiate delayed zlib (zlib@openssh.com) — Cyberduck, or OpenSSH sftp -C — complete the SFTP handshake (INIT / REALPATH / STAT all succeed) and then the connection dies, with the server logging SshEncoding: length invalid on the next inbound packet.

Root cause

russh's delayed-zlib transport. Compression activates after auth; a few packets later the flate2 stream desyncs, so russh reads the next packet's length prefix out of corrupted plaintext and aborts the session.

Confirmed it is compression, not the SFTP layer or the channel-close path:

client compression negotiated result
OpenSSH sftp (default) none ✅ works
FileZilla none ✅ works
paramiko none ✅ works
OpenSSH sftp -C zlib@openssh.com SshEncoding: length invalid
Cyberduck 9.5 zlib@openssh.com SshEncoding: length invalid

Reproduces on both russh 0.61.1 (current fork) and 0.62.1, so it is independent of the SFTP session-close behaviour.

One-command repro (before this patch)

sftp -C -P 2223 user@host      # -> "SshEncoding: length invalid", connection closed

Fix

build_russh_config now advertises only none:

const NO_COMPRESSION: &[russh::compression::Name] = &[russh::compression::NONE];
let preferred = russh::Preferred {
    compression: std::borrow::Cow::Borrowed(NO_COMPRESSION),
    ..russh::Preferred::DEFAULT
};

Every client falls back to the uncompressed transport. This matches the Dropbear / OpenSSH sftp-server defaults used in Backend.AI kernel containers, where compression was never in play — so there is no functional loss for the intended deployment.

Verification

  • sftp -C now negotiates server_compression: None and lists / downloads cleanly.
  • Live Cyberduck 9.5 session connects and browses without the mid-session drop.
  • cargo build / cargo clippy clean on main (russh 0.61.1 fork).

The underlying russh delayed-zlib desync is left to be fixed upstream; this patch is the safe server-side workaround.

Fixes #215.

…com desync

Clients that negotiate delayed zlib (`zlib@openssh.com`) — Cyberduck,
`sftp -C` — completed the SFTP handshake and then dropped mid-session with
the server logging `SshEncoding: length invalid` on the next inbound packet.

Root cause is russh's delayed-zlib transport: the flate2 stream desyncs a
few packets after compression activates post-auth, so russh reads the next
packet's length prefix out of corrupted plaintext. Reproducible on both
russh 0.61.1 and 0.62.1 (rules out the channel-close path; it's compression,
not the SFTP layer). OpenSSH's default `sftp`, FileZilla, and paramiko all
negotiate `none` and were unaffected.

Set `russh::Preferred { compression: [NONE], ..DEFAULT }` in
`build_russh_config` so the server advertises only `none` and clients fall
back to the uncompressed transport — matching the Dropbear / OpenSSH
sftp-server defaults used in Backend.AI kernel containers, where compression
was never in play. Verified with `sftp -C` (negotiates `compression: none`)
and a live Cyberduck 9.5 session.

Fixes lablup#215.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

SFTP: Cyberduck (sshj) connection dropped on initial REALPATH/STAT — server errors SshEncoding: length invalid

1 participant