Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## [4.1.0] - Unreleased
## [4.1.0] - 2026-09-04
- Added `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].
- Added a `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].
- Fixed the package reading as web-incompatible on pub.dev. `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].
Expand Down
40 changes: 22 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as e

## ✨ Features

- **Pure Dart**: Working with both Dart VM and Flutter.
- **Pure Dart**: Runs on the Dart VM, Flutter and the web. Browser support is checked in CI against a real OpenSSH server, see [Web support](#web-support).
- **SSH Session**: Executing commands, spawning shells, setting environment variables, pseudo terminals, etc.
- **Authentication**: Supports password, in-memory private keys (`SSHKeyPair`), external asynchronous identities (`SSHIdentity` for Secure Enclave, YubiKey/FIDO2, smart cards, OS agents), RFC 4252 §7.8 public-key probing, RFC 4252 §9 hostbased authentication, and keyboard-interactive authentication.
- **Forwarding**: Supports local forwarding, remote forwarding, and dynamic forwarding (SOCKS5 CONNECT).
Expand Down Expand Up @@ -167,30 +167,34 @@ already trust end to end.

### Web support

Direct native TCP sockets are not available in browsers, so this will fail on
Flutter Web / Dart Web:
dartssh2 runs on Flutter Web and Dart Web. The protocol, authentication,
sessions, channel forwarding and SFTP all work compiled to JavaScript, and CI
checks it rather than assuming it: on every pull request a browser completes a
handshake, runs a command, holds an `aes256-gcm` session and round-trips a file
over SFTP against a real OpenSSH server.

**You have to supply the transport.** Browsers cannot open TCP sockets, so this
fails:

```dart
await SSHSocket.connect('host', 22);
```

For web apps, use a custom `SSHSocket` transport over a browser-supported
channel (for example, a WebSocket tunnel/proxy to your SSH endpoint).

Before 4.0.0 that was all it said, and it was not enough: every AEAD cipher and
the whole of SFTP went through `ByteData.getUint64`/`setUint64`, which throw
under dart2js, so a browser connection died at the first encrypted packet even
with a correct transport. That is fixed, and a `dart test -p chrome` job now
guards it.
`SSHSocket` is an interface, so give it something a browser does have, normally
a WebSocket to a proxy that bridges to your SSH endpoint. For a minimal working
pair of both ends, see [`tool/ws_bridge.dart`](tool/ws_bridge.dart) and the
socket in
[`test/src/integration/web_interop_test.dart`](test/src/integration/web_interop_test.dart).

Two caveats remain. `chacha20-poly1305@openssh.com` cannot be used on the web,
because PointyCastle's Poly1305 requires full-width 64-bit integers. It sits
third in the default cipher list, so AES-GCM or AES-CTR is normally negotiated
and nothing needs doing. Only pinning ChaCha20-Poly1305 explicitly will fail.
Two things are not available in a browser:

And `SftpFile.downloadToRandomAccess` is not available, since it takes a
`dart:io` `RandomAccessFile` and there is no local file to hand it. Use
`SftpFile.downloadTo`, which takes a sink, or `SftpFile.read`.
- `chacha20-poly1305@openssh.com`, because PointyCastle's Poly1305 needs
full-width 64-bit integers. It sits third in the default cipher list, so
AES-GCM or AES-CTR is negotiated instead and nothing needs doing. Only
pinning it explicitly will fail.
- `SftpFile.downloadToRandomAccess`, which takes a `dart:io`
`RandomAccessFile`, and there is no local file to hand it. Use
`SftpFile.downloadTo`, which takes a sink, or `SftpFile.read`.

### Customize client SSH identification

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: dartssh2
version: 4.0.1
version: 4.1.0
description: SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use.
homepage: https://github.com/vicajilau/dartssh2
repository: https://github.com/vicajilau/dartssh2
Expand Down
Loading