NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #1188 most downloaded on pub.dev
SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use.
Last release 1 months ago
04 Sep 2026
Ships unpredictably
gaps range from 8 days to 9 months
Nearly every release is documented
notes for 50 of 50 stable releases
Nothing withdrawn
no release was ever pulled
5 years old
62 releases · first in 2021
One column per quarter.
perf(client): add SSHClient.pipelineChannelRequests, off by default by @michnovka in #246
Full Changelog: v4.0.1...v4.1.0
SSHClient.pipelineChannelRequests, off by default, which sends all of a session's channel requests before reading any reply instead of waiting for each one in turn. execute and shell send env, agent forwarding, pty-req and x11-req ahead of exec or shell, and each of them cost a round trip: against a server 40 ms away, execute with a pty measured 246 ms before and 206 ms after. RFC 4254 §5.4 permits sending further messages without waiting and §4 requires the peer to answer a channel's requests in the order it received them, which is what ssh(1) relies on when it does the same thing. Setting it also adopts OpenSSH's reporting, because it has to: the command is on the wire before a refusal can come back, so a refused pty-req, env, agent forwarding or x11-req is reported through printDebug and the command runs, the way ssh(1) prints PTY allocation request failed on channel 0 and carries on, rather than throwing an error that means the command has already run. Only a refused exec or shell still throws SSHChannelRequestError, because nothing has run when that one fails. Leaving it unset changes nothing, down to the order the requests go out and the message of every error [#243].dartssh2-nopty account to the interop server, which PermitTTY no applies to, so both sides of a refused pty-req are exercised against a real OpenSSH: by default the command does not run, and with pipelining it does [#243].SftpFile.downloadToRandomAccess takes a dart:io RandomAccessFile, and naming that type from the SFTP library was enough for pub.dev to drop platform:web from the whole package, which also keeps it out of any search filtered to web. Everything else already compiled and ran there, and 4.0.0 made the ciphers and SFTP work. The method moves to an SftpFileDownload extension in its own library, exported conditionally the way SSHSocket and dynamic forwarding already are, so nothing changes for a caller on the VM: same import, same call. On the web the extension is simply absent, as is the RandomAccessFile it would need. Confirmed with pana, the tool pub.dev scores with: platform:web is present after and absent before [#248].-p chrome until now proved the AEAD arithmetic, the SFTP encoding and the HTTP parsing compile and behave, and none of it put a packet on a wire, which is the gap that let every AEAD cipher and the whole of SFTP sit broken on the web until 4.0.0. The new tests run the browser against the same OpenSSH server the VM interop tests use, through tool/ws_bridge.dart, and cover a handshake, a command, an aes256-gcm session and an SFTP round trip. The SSHSocket they connect through is the WebSocket one the README tells web users to write, so it doubles as a worked example [#248].fix(channel): flush the window grant on first listen, and grant at a threshold by @michnovka in #244
Full Changelog: v4.0.0...v4.0.1
StreamController.isPaused is true until something listens, which suppressed every SSH_MSG_CHANNEL_WINDOW_ADJUST, and Dart delivers the first subscription as onListen rather than onResume — the only hook wired — so a peer that legally filled the advertised window in that gap was left at zero credit with no further data able to arrive and trigger a grant. Reproduced at the client's own sizes with 64 packets of 32 KiB into a 2 MiB window, before and after the first listener and through .map(), in every case zero adjustments. The remote forwarding example in the README reaches it: it awaits Socket.connect() for each connection before subscribing, and the forwarded channel is created with no listener attached [#244].SSH_MSG_CHANNEL_DATA was answered with a window adjust of its own, and the smaller the packets the closer the uplink packet count got to the downlink one: counted on SSHChannelController, 256 inbound packets of 32 KiB produced 256 adjustments, and 4096 of 64 B produced 4096. They now produce 8 and 0. This is one of the two rules OpenSSH applies in channels.c, which refills at half the window or once local_window_max - local_window > local_maxpacket * 3, whichever comes first. Only the first is implemented here, so at a 2 MiB window with 32 KiB packets this defers further than OpenSSH would, thirty-two packets against its three or four, which is the point of the change. The threshold never defers past one maximum packet of remaining credit, so a window smaller than twice the packet size cannot leave a conforming peer holding a chunk it may neither send nor is obliged to split. Pausing the stream still suppresses the grant, which is the documented backpressure mechanism [#244].Drop the legacy analyzer plugin entry that now fails analysis by @vicajilau in #232
Full Changelog: v3.3.1...v4.0.0
diffie-hellman-group14-sha1 and diffie-hellman-group-exchange-sha1, the ssh-rsa host key signature, and the aes256-cbc and aes128-cbc ciphers. This matches what OpenSSH proposes in myproposal.h, which drops all of them and keeps only hmac-sha1 at the end of the MAC list, as this does. ssh-rsa signs host keys with SHA-1 and is open to chosen-prefix collisions, which is why OpenSSH disabled it by default in 8.8, and CBC in SSH is vulnerable to the plaintext recovery of CVE-2008-5161. A server that offers nothing but these will now fail to negotiate rather than connect weakly, which for older routers, NAS boxes and embedded servers is a real change: pass the algorithm through SSHAlgorithms to keep talking to it [#236]. Thanks [@GT-610].DEFAULT_NUM_REQUESTS. Writes were issued and awaited one at a time, so every chunk cost a full round trip and a high latency link spent most of its time idle. Acknowledgements are now accepted out of order without resubmitting an offset, and offsets are still assigned in stream order so concurrent writes cannot overlap. SftpFile.write and SftpFileWriter take chunkSize and maxPendingRequests, and reject a negative offset or a non-positive setting rather than misbehaving later [#237]. Thanks [@GT-610].SftpFileWriter error handling added in #230, whose regression tests all still pass [#237].chunkSize in favour of defaultChunkSize, and maxBytesOnTheWire, which nothing reads any more now that uploads are bounded by request count rather than by a byte window [#237].ChunkBuffer to grow by reallocating with headroom instead of copying the whole buffer on every append, making add() amortised O(1) rather than O(n). Accumulating 64 MB in 8 KiB chunks without draining, which is what a burst of packets arriving faster than they are consumed looks like, drops from about 185 seconds to about 72 milliseconds. The steady drained case costs about 25% more, roughly 162 ms to 202 ms over 20k packets of 32 KiB, because consume() now returns a copy rather than an alias into a buffer a later add() may reallocate; both figures are around 3 GB/s, so neither is visible next to a network [#231].MinChunkSize stream transformer and two stale markers in ssh_mac_type.dart, neither of which anything referenced [#231].analysis_options.yaml. Dart 3.13.2 warns on it and dart analyze exits non-zero on a warning, so every job on every branch started failing with nothing in the code having changed. It was dead configuration in any case: dart_code_metrics_presets ships preset YAML meant for include:, no analyzer plugin, so nothing was ever loaded through it. The now unused dev dependency went with it [#232] [#233].dart:io for their ssh-keygen interoperability checks, which the web job cannot load [#239].SftpFileAttrs now drops a uidgid or acmodtime pair when only one half of it is set, instead of writing the flag with a single value. The pair is two fields under one flag in the SFTP wire format, so a half-filled one produced a packet the server misparsed, applying the wrong ownership or timestamps. Anyone calling setStat with only modifyTime set will find the value is now ignored rather than sent alongside a garbage access time; set both to change either [#230]. Thanks [@klc].SSHHostkeyError, as OpenSSH does. The signature was already re-checked on every exchange, but that only proved the key presented was self-consistent, not that it was the key onVerifyHostKey had already approved, so a server could hand out one key at connect time and a different one on the first rekey. A connection that used to survive this will now drop. onVerifyHostKey is consulted once per connection, not once per exchange [#229]. Thanks [@klc].keyboard-interactive responses being written to the trace log in plaintext, which put the user's password in any log a caller collected with printTrace set [#229].1 < f < p - 1, the NIST curves reject points that fail to decode, lie off the curve or are the point at infinity, and X25519 rejects small-order points via the RFC 8731 §3 all-zero shared secret check and requires the key to be exactly 32 bytes. Without these a peer could force a shared secret it knew in advance [#229].SSHPacketError instead of a RangeError that no SSHError handler would catch [#229].RangeError that followed was not an SSHError any handler would catch. The length is now rejected up front, as OpenSSH does in ssh_packet_read_poll2(), and the remaining ciphertext is decrypted in one pass instead of a block at a time [#234]. Thanks [@GT-610].SSH_MSG_KEX_ECDH_REPLY being encoded with its fields in the wrong order. RFC 5656 §4 specifies K_S, Q_S, signature, which is what this library's own decoder already expected, so only a peer decoding what dartssh2 sent as a server was affected [#229].writeMpint(BigInt.zero) emitting 00 00 00 01 00 where RFC 4251 §5 requires a zero-length string, and readNameList returning [''] for an empty name-list [#229].onVerifyHostKey null accepts any host key, which makes the connection trivially interceptable. The parameter is optional and the behaviour was not stated anywhere [#229].OpenSSHKeyPair.toPem() takes an optional passphrase, and a non-empty one encrypts the private section with aes256-ctr keyed by bcrypt_pbkdf, following what sshkey_private_to_blob2() writes: a 16-byte salt, 24 rounds, key and IV derived together in one call, the check integer written twice and the block padded with 1, 2, 3 and so on. A null or empty passphrase still writes the unencrypted form. OpenSSHKeyPairs.encrypted builds the container directly for callers that need it. The SSHKeyPair interface is unchanged, so the new arguments are reachable only through OpenSSHKeyPair [#235]. Thanks [@GT-610].bcrypt_pbkdf reports invalid parameters through its return value, which was discarded, so a key with a zero round count or an empty salt carried on with an underived key and failed later as a check-integer mismatch. It now raises SSHKeyDecryptError at the point the KDF fails [#235]. Thanks [@GT-610].SSHClient.rekey(), which starts a new key exchange on an established connection and returns a Future<void> completing once the new keys are in effect, so a caller rekeying between transfers can await it instead of watching done. If an exchange is already running, whether this side or the server started it, no second one is sent and the future tracks the one in flight; if the connection ends first the future carries the error that ended it. SSHTransport.rekey() was already public and returns the same future now, where it used to return void [#229].ByteData.getUint64 and setUint64 throw Unsupported operation: Uint64 accessor not supported by dart2js, and four call sites went through them: the message reader, the int helper, the AES-GCM nonce and the ChaCha20-Poly1305 nonce. Since aes256-gcm@openssh.com sits first in the default cipher list, a browser connection died at the first encrypted packet even with a correct custom SSHSocket. Each now reads and writes two 32-bit words instead, bit-for-bit identical on the VM; compiled to JS a value needing more than 53 bits raises UnsupportedError rather than silently rounding, since these back SFTP file sizes and offsets [#230].test-web CI job running dart test -p chrome, with @TestOn markers on the suites that genuinely need the VM. Nothing ran against dart2js before, which is how the above went unnoticed while the README listed web as supported [#230]./proc and /dev being handed back as empty. They report a stat size of 0 while still returning data, and read, downloadTo and downloadToRandomAccess all trusted the reported size and returned before issuing a read. Reads are now EOF-driven when the size cannot be trusted [#230].SftpFileWriter hanging forever when the local stream raised. Errors had no handler and _handleLocalDone completed the done future unguarded, so a failing upload never returned and could also double-complete [#230].Content-Length. The body was read only up to a length that stayed 0, so whether it survived depended on TCP segment boundaries [#230].HttpHeaders.host and HttpHeaders.port returning null for a perfectly valid Host header [#230].HEAD, has no body whatever its framing headers say, so a server that keeps the connection open after sending one left the read waiting for bytes that were never coming. This was the sharp edge of reading unframed bodies to end of stream, added above [#230].SSHHttpClient.idleTimeout, which bounds the wait between two pieces of a response. A body delimited by connection close still has to be read to end of stream, and nothing else in the client bounded that, so a peer that stopped sending without closing could hang a request indefinitely. It is an inactivity timeout rather than a deadline for the whole response, so a large body that keeps arriving is never cut short; leaving it null keeps the unbounded behaviour [#230].fix: stop offloading elliptic curve key exchange to isolates by @vicajilau in #227
Full Changelog: v3.3.0...v3.3.1
Isolate.run takes several times that to spawn and tear down, and a client pays it twice per handshake. On a memory constrained Android device the spawn delay was long enough for the server to time out the key exchange and close the connection before SSH_MSG_NEWKEYS went out, surfacing as SSHAuthAbortError with a null reason [#226]. Thanks [@cesarcamps].onVerifyHostKey callback. Everything between receiving the key exchange reply and sending SSH_MSG_NEWKEYS used to run without a single printDebug call, so a slow user callback and a slow shared secret were indistinguishable in a trace, and both looked like a hung handshake [#226].feat: deprecate SSHTransport.onPacket in favour of onMessage by @vicajilau in #221
Full Changelog: v3.2.0...v3.3.0
SSHDisconnectError, so the reason the peer gave for terminating the connection reaches the caller. SSH_MSG_DISCONNECT carries a reason code and a description, which is where OpenSSH puts lines such as "no matching key exchange method found"; the transport used to log it and close cleanly, leaving an unexplained disconnection [#224].SSHMessageReader.readBytes() indexing the underlying buffer instead of the message, so it returned the wrong bytes whenever the message was a view into a larger buffer, which is what every SSH and SFTP payload is. Only OpenSSH private key decoding called it, on a freshly decoded blob where the two coincide, so nothing was broken in practice [#223].SSHPacketError instead of RangeError or IndexError. Every decoder that parses peer-supplied bytes went through the latter, which are how Dart reports a bug in the caller: a handler catching SSHError missed them, and inside a stream callback they escaped as uncaught errors [#223].SSHTransport.onPacket in favour of onMessage, which reports whether it recognized a message so the transport can answer unknown ones as RFC 4253 requires. onPacket keeps working [#221].Add hostbased authentication and methodsLeft handling by @GT-610 in #218
Full Changelog: v3.1.0...v3.2.0
chacha20-poly1305@openssh.com packet cipher, implemented as OpenSSH's own construction rather than the RFC 8439 AEAD: two independent ChaCha20 keys, a separately encrypted packet length, and Poly1305 over the raw encrypted length and body. It joins the default cipher list in third place, after the two AES-GCM variants, so it is negotiated with servers that do not offer AES-GCM [#217]. Thanks [@GT-610].SSHIdentity API, with the new SSHClient.hostbasedIdentities, SSHClient.hostName and SSHClient.userNameOnClientHost options. The host key blob type is kept separate from the signature algorithm, so RSA SHA-2 signatures work [#218]. Thanks [@GT-610].methodsLeft as an allow-list while keeping the client's own preference order, to keep publickey and hostbased available only while identities remain, and to reset publickey state after a partial success, as OpenSSH does [#218]. Thanks [@GT-610].SSH_MSG_UNIMPLEMENTED handling. Genuinely unrecognized messages are now reported with the rejected packet's own sequence number, while SSH_MSG_IGNORE, SSH_MSG_DEBUG and incoming SSH_MSG_UNIMPLEMENTED are consumed without creating reply loops. Unexpected messages during the initial strict key exchange disconnect, matching OpenSSH, while rekeys reply instead [#216]. Thanks [@GT-610].SSHTransport.onMessage, a handler that reports whether it recognized a message so the transport knows when to reply SSH_MSG_UNIMPLEMENTED. The existing onPacket keeps working unchanged and assumes every packet it receives is handled [#216]. Thanks [@GT-610].SFTP_MAX_MSG_LENGTH in OpenSSH, with the four-byte length prefix excluded. Without it a peer could declare an arbitrarily large packet and make the client buffer indefinitely while waiting for a body that never arrives [#215]. Thanks [@GT-610].Create SECURITY.md by @vicajilau in #205
Full Changelog: v3.0.2...v3.1.0
SSH_MSG_CHANNEL_CLOSE, channel destruction and transport termination to be terminal for pending replies, while SSH_MSG_CHANNEL_EOF remains non-terminal, since RFC 4254 allows request replies to arrive after EOF [#212]. Thanks [@GT-610].kex-strict-c-v00@openssh.com), the countermeasure against the Terrapin attack (CVE-2023-48795). It is negotiated automatically and, when the server supports it, packet sequence numbers are reset after every SSH_MSG_NEWKEYS, SSH_MSG_IGNORE / SSH_MSG_UNIMPLEMENTED / SSH_MSG_DEBUG are rejected during a key exchange, and the first SSH_MSG_KEXINIT is required to be the first packet of the connection. Exposed as SSHClient.strictKex [#207]. Thanks [@vicajilau].SSH_MSG_EXT_INFO support (RFC 8308). The client advertises ext-info-c and exposes the signature algorithms the server accepts as SSHClient.serverSigAlgs [#207]. Thanks [@vicajilau].ssh-rsa (SHA-1) is now last among the host key algorithms. CBC ciphers and hmac-sha1 remain available but are only reached when a server offers nothing better [#207]. Thanks [@vicajilau].diffie-hellman-group1-sha1 (1024-bit group), hmac-md5, and the truncated hmac-sha2-[256|512]-96 variants. They are still implemented and can be re-enabled by passing them to SSHAlgorithms explicitly [#207]. Thanks [@vicajilau].SSH_Message_Userauth_Request.decode() swapping the old and new password when decoding a password change request, contrary to RFC 4252 §8 [#207]. Thanks [@vicajilau].SSH_Message_Userauth_Request.decode() not reading the boolean that precedes the algorithm name in a publickey request (RFC 4252 §7), which misparsed every signed request and could not represent an unsigned probe [#207]. Thanks [@vicajilau].SECURITY.md with a private vulnerability reporting process [#207]. Thanks [@vicajilau].onVerifyHostKey in the README. Host key signatures were and are always verified, but deciding whether the key is the expected one is the caller's job, and omitting the handler accepts any host key [#207]. Thanks [@vicajilau].chore: point repository URLs at vicajilau/dartssh2 by @vicajilau in #202
Full Changelog: v3.0.1...v3.0.2
SftpFile.read() and SftpClient.download() no longer return truncated, misaligned data [#200] [#203]. Thanks [@GT-610].1 <= x < n [#201]. Thanks [@GT-610].SftpFile.read() to process pipelined read replies as they arrive while still emitting chunks ordered by file offset [#200]. Thanks [@GT-610].SftpFile.read() to throw SftpError when a server returns more bytes than requested, instead of silently truncating the surplus [#200]. Thanks [@GT-610].Fix SSHChannel.remoteChannelId by @GT-610 in #196
Full Changelog: v3.0.0...v3.0.1
x11-req screen number as a uint32 instead of a string, as required by RFC 4254 §6.3 [#194]. Thanks [@GT-610].SSHChannel.remoteChannelId returning the local channel id instead of the id assigned by the peer [#196]. Thanks [@GT-610].SSHClient.run() and SSHClient.runWithResult() hanging forever when the stdout stream emitted an error, by routing stdout errors to the stdout completer [#195]. Thanks [@GT-610].SSHClient.run() and SSHClient.runWithResult() raising an uncaught error, instead of throwing to the caller, when the stderr stream emitted an error while stdout was still open. Both streams are now awaited together, and the session is closed on failure so the SSH channel is no longer leaked [#197].Random.secure() source and widened byte generation to the full 0x00-0xff range, covering key exchange cookies, ephemeral key exchange private values, and OpenSSH private key encryption seeds [#193]. Thanks [@GT-610].SSHKeyPair.toPem() to the same secure random source, removing the last insecure Random() usage in the library [#198].feat: support asynchronous external identities, RFC 4252 probing, and async close ( #190 ) by @vicajilau in #192
Full Changelog: v2.22.5...v3.0.0
SSHClient.identities getter type from List<SSHKeyPair>? to List<SSHIdentity>? to support asynchronous external signers (OS agents, hardware tokens, smart cards, Secure Enclave, Android Keystore, and custom signers) [#190]. Constructor invocations passing List<SSHKeyPair> remain 100% source-compatible.SSHClient.close() return type from void to Future<void> to allow awaiting complete socket and channel teardown.SSHIdentity abstraction, SSHRawHostKey, and SSHRawSignature with optional comment and shouldProbe properties [#190].SSH_Message_Userauth_PK_Ok and SSHIdentity.shouldProbe to check server key acceptance before requesting hardware token / user interaction.src/ssh_identity.dart and src/ssh_hostkey.dart in lib/dartssh2.dart.fix: export ssh_userauth.dart in public API ( #188 ) by @vicajilau in #189
Full Changelog: v2.22.4...v2.22.5
src/ssh_userauth.dart in lib/dartssh2.dart to expose SSHUserInfoRequest, SSHUserInfoPrompt, SSHAuthMethod, and SSHChangePasswordResponse [#188]. Thanks [@vicajilau].Advertise the RFC 8731 kex name curve25519-sha256 by @nickn17 in #187
curve25519-sha256 alongside legacy curve25519-sha256@libssh.org [#187]. Thanks [@nickn17].fix(sftp): close the underlying channel in SftpClient.close() by @keinstn in #186
SftpClient.close() by closing the underlying SSH channel and returning Future<void> to allow awaiting channel teardown [#186]. Thanks [@keinstn].Added flush() to SSHSocket, SSHClient, and SSHChannel to allow force flushing of buffered outgoing data [#183]. Thanks [@vicajilau].
flush() to SSHSocket, SSHClient, and SSHChannel to allow force flushing of buffered outgoing data [#183]. Thanks [@vicajilau].Fixed a keepalive issue where overlapping pings could occur and caught errors during ping execution. Thanks [@vicajilau].
Added optional handshakeTimeout and authTimeout to SSHClient to limit connection negotiation and user authentication times [#182]. Thanks [@GT-610].
handshakeTimeout and authTimeout to SSHClient to limit connection negotiation and user authentication times [#182]. Thanks [@GT-610].Fixed an SSHTransport busy-loop (100% CPU / ANR) that occurred when a partial packet remained in the read buffer [#179]. Thanks [@vicajilau].
SSHTransport busy-loop (100% CPU / ANR) that occurred when a partial packet remained in the read buffer [#179]. Thanks [@vicajilau].Added SSHSession.waitForExit({Duration? timeout}) to await remote process exit status with an optional timeout [#176]. Thanks [@GT-610].
SSHSession.waitForExit({Duration? timeout}) to await remote process exit status with an optional timeout [#176]. Thanks [@GT-610].BREAKING: Bumped the minimum Dart SDK constraint to 3.0.0 [#23]. Thanks [@vicajilau].
3.0.0 [#23]. Thanks [@vicajilau].OpenSSHKeyPair as a mixin class to comply with Dart 3.0 class modifier rules [#23]. Thanks [@vicajilau].Isolate.run on platforms that support it, preventing the Flutter main thread from blocking/freezing during connection [#23]. Thanks [@vicajilau].Added tolerant HTTP-date parsing to accept all RFC 7231 §7.1.1.1 HTTP-date formats (IMF-fixdate, RFC 850, asctime) for HTTP response headers [#170]. T
IMF-fixdate, RFC 850, asctime) for HTTP response headers [#170]. Thanks [@GT-610].posix-rename@openssh.com SFTP extension to perform atomic renames with POSIX semantics (replace destination if it exists) when advertised by the server [#172]. Thanks [@GT-610].SftpFile.downloadToRandomAccess to download a remote file directly into a dart:io RandomAccessFile using out-of-order pipelined writes, maximizing download performance on high-latency links [#173]. Thanks [@GT-610].Fixed AES-GCM cipher encryption and decryption sequence number/nonce counter resetting during key exchanges [#165]. Thanks [@vicajilau].
SSHHostkeyVerifyHandler now receives an OpenSSH-style SHA256:<base64> host key fingerprint instead of the previous raw MD5 digest, so host key pinning code must be updated accordingly [#162]. Thanks [@thyssentishman].Made SSHPem.decode accept CRLF (\r\n) line endings in addition to LF when parsing PEM content [#157]. Thanks [@gkc].
SSHPem.decode accept CRLF (\r\n) line endings in addition to LF when parsing PEM content [#157]. Thanks [@gkc].Improved Web/WASM compatibility by updating SSHSocket conditional imports so web runtimes consistently use the web socket shim and avoid incorrect nat
SSHSocket conditional imports so web runtimes consistently use the web socket shim and avoid incorrect native socket selection [#88]. Thanks [@vicajilau].SSHClient.forwardDynamic) with SOCKS5 NO AUTH + CONNECT, including configurable handshake/connect timeouts and connection limits.aes128-gcm@openssh.com, aes256-gcm@openssh.com) AEAD groundwork in transport and cipher negotiation; currently opt-in (not enabled by default yet). chacha20-poly1305@openssh.com remains pending [#26]. Thanks [@vicajilau].BREAKING: Changed SSHChannelController.sendEnv() from void to Future to properly await environment variable setup responses and avoid race conditions
SSHChannelController.sendEnv() from void to Future<bool> to properly await environment variable setup responses and avoid race conditions with PTY requests [#102]. Thanks [@itzhoujun] and [@vicajilau].example/shell.dart against missing local terminal handles (for example GUI-launched Windows .exe) [#121]. Thanks [@bradmartin333] and [@vicajilau].EC PRIVATE KEY PEM format in SSHKeyPair.fromPem [#109]. Thanks [@jooy2] and [@vicajilau].SSHClient.runWithResult() to expose command output together with exitCode and exitSignal while keeping run() as a convenience API [#99]. Thanks [@falrom] and [@vicajilau].download() / downloadTo() APIs and read pipeline tuning knobs (chunkSize, maxPendingRequests) for improved large-file throughput while preserving stream compatibility [#124]. Thanks [@vicajilau].FormatException on non-UTF-8 server filenames [#95]. Thanks [@vicajilau].Updated pointycastle dependency to ^4.0.0 [#131]. Thanks [@vicajilau].
pointycastle dependency to ^4.0.0 [#131]. Thanks [@vicajilau].SSHClient [#135]. Thanks [@Remulic] and [@vicajilau].SSHAuthAbortError through reason for better diagnostics [#133]. Thanks [@james-thorpe] and [@vicajilau].SSH-1.99-* server banners as SSH-2 compatible during version exchange and added regression tests [#132]. Thanks [@james-thorpe] and [@vicajilau].auth-agent-req@openssh.com) with in-memory agent handling and RSA sign-request flag support [#139]. Thanks [@Wackymax] and [@vicajilau].SSHHttpClientResponse to handle CRLF endings consistently and avoid trailing line-ending artifacts in parsed status/header fields [#145]. Thanks [@vicajilau].SftpInitPacket.decode now parses extension pairs correctly and SftpExtendedReplyPacket.encode now preserves raw payload bytes [#145]. Thanks [@vicajilau].Fixed SSH connections through bastion hosts where the target server sends its version string immediately upon connection (which is standard behavior p
docs: Update NoPorts naming [#115]. [@XavierChanth].
Fixed streams and channel not closing after receiving SSH_Message_Channel_Close [#116]. [@cbenhagen].
Fixed Type 'Uint8' not found issue.
Bug fix in SftpFileWriter for [#50], [#71], [#100].
Make the type of SSHForwardChannel.sink to StreamSink > to match its super class.
SSHForwardChannel.sink to StreamSink<List<int>> to match
its super class.SSHHttpClient for easy http request forwarding.Better handling of channel close.
SSHForwardChannel implement SSHSocket for better interoperability.Make SftpFileWriter implement Future for backward compatibility.
SftpFileWriter implement Future<void> for backward compatibility.- Export SftpFileWriter
SftpFileWriterSftpFile.write now returns a SftpFileWriter that can be used to control the writing process.
SftpFile.write now returns a SftpFileWriter that can be used to control
the writing process.SftpClient.statvfs and SftpFile.statvfs.Move cli into separate package.
- Update README.md
- Update README.md
- Update README.md
Fix bug in exporting openssh private key to pem, thanks [@PIDAMI]
pinenacl to 0.5.0.Upgrade rsa authentication algorithm to rsa-sha2-256.
Support encrypted RSA format private key
Allow username with @ in dartssh2 command [#24]
@ in dartssh2 command [#24]Allow ignoring stdout or stderr in SSHClient.run.
SSHClient.run.SSHAuthFailError and SSHAuthAbortError.Nothing published for this version
Ignore remote data after channel closed.
- Fix lint errors
- Remove unused dependencies - Fix lint errors
Fix null check error in kill() [#17]
kill() [#17]Limit the maximum size of channel packets
Support session stdin streaming and EOF
Support ssh v2 when version string does not contain CR [#14], thanks [@Migarl]
Add remoteVersion field to SSHClient
Add description field in SSHChannelOpenError
- Update README.md - Support export keypair to PEM
Support loading OpenSSH encrypted pem files.
Implements local port forwarding
dartsftp commanddartssh command now supports login with public key.
dartssh command now supports login with public key.dartssh command now supports terminal window resize.
dartssh command now supports terminal window resize.- Fix typos.
Your coding agent can read these notes before it upgrades. Set up the MCP server →