Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2cbbc3a
fix: harden dynamic forwarding, ssh-agent, and EC key parsing
GT-610 Jul 1, 2026
4e707e1
Format
GT-610 Jul 1, 2026
f4b3e9e
feat: add SSHSession waitForExit timeout
GT-610 Jul 1, 2026
ab12899
Merge pull request #175 from lollipopkit/upstream/dynamic-forward-age…
vicajilau Jul 1, 2026
942707c
chore: bump version to 2.21.0 and harden SOCKS5, SSH agent, and EC ke…
vicajilau Jul 1, 2026
6a9ac34
Merge pull request #176 from lollipopkit/upstream/session-wait-for-ex…
vicajilau Jul 1, 2026
1d3ed4a
feat: add waitForExit with optional timeout to SSHSession
vicajilau Jul 1, 2026
1c59654
fix: prevent SSHTransport busy-loop on partial packets
vicajilau Jul 2, 2026
9078dfe
test: add test case for rescheduling transport processing when extra …
vicajilau Jul 2, 2026
b53960a
style: wrap test description line in ssh_transport_version_test.dart
vicajilau Jul 2, 2026
212074e
Merge pull request #179 from TerminalStudio/fix/transport-partial-pac…
vicajilau Jul 2, 2026
2733387
chore: bump version to 2.21.1 and update CHANGELOG
vicajilau Jul 2, 2026
84a3166
chore: add github-actions ecosystem and timezone configuration to dep…
vicajilau Jul 2, 2026
ba0a5bc
chore(deps): bump actions/checkout from 6 to 7
dependabot[bot] Jul 2, 2026
7489686
chore(deps): bump codecov/codecov-action from 5 to 7
dependabot[bot] Jul 2, 2026
a2719d2
chore: disable CI failure on codecov upload error in workflow
vicajilau Jul 2, 2026
00589de
Merge branch 'master' into dependabot/github_actions/actions/checkout-7
vicajilau Jul 2, 2026
f4927ab
Merge branch 'master' into dependabot/github_actions/codecov/codecov-…
vicajilau Jul 2, 2026
b93e182
Merge pull request #181 from TerminalStudio/dependabot/github_actions…
vicajilau Jul 2, 2026
c44b7a8
Merge branch 'master' into dependabot/github_actions/actions/checkout-7
vicajilau Jul 2, 2026
c2bf398
Merge pull request #180 from TerminalStudio/dependabot/github_actions…
vicajilau Jul 2, 2026
796b42e
feat: add SSHClient handshake and auth timeouts
GT-610 Jul 1, 2026
3982646
Merge pull request #182 from lollipopkit/upstream/client-handshake-au…
vicajilau Jul 3, 2026
23da78d
feat: add optional handshakeTimeout and authTimeout to SSHClient
vicajilau Jul 3, 2026
fa64eb7
fix(keepalive): prevent overlapping pings and catch errors
vicajilau Jul 13, 2026
7de4469
chore: added SSHKeepAlive tests
vicajilau Jul 13, 2026
8b9252e
fix: resolve keepalive ping overlap and add error handling in version…
vicajilau Jul 13, 2026
a1fdb16
Merge pull request #184 from vicajilau/fix/keepalive-robustness
vicajilau Jul 13, 2026
d8df20a
feat(socket): add flush() to SSHSocket, SSHClient and SSHChannel
vicajilau Jul 14, 2026
7cac210
docs(changelog): add entry for 2.22.2 with flush() changes
vicajilau Jul 14, 2026
f8cac95
chore: update release dates in changelog
vicajilau Jul 14, 2026
88c2859
Merge branch 'master' into fix/keepalive-robustness
vicajilau Jul 14, 2026
f2e8118
chore: update release date for version 2.22.0 in CHANGELOG.md
vicajilau Jul 14, 2026
1047270
test: add unit tests for flush() to cover new API
vicajilau Jul 14, 2026
f83aae1
test: add unit tests for flush functionality across sockets, transpor…
vicajilau Jul 14, 2026
233bfed
Merge pull request #185 from vicajilau/fix/keepalive-robustness
vicajilau Jul 14, 2026
f0c06de
chore: bump version to 2.22.2 and update changelog date
vicajilau Jul 15, 2026
3e9ab0e
fix(sftp): close the underlying channel in SftpClient.close()
keinstn Jul 20, 2026
90e5272
fix: resolve SSH channel leak in SftpClient.close() and update versio…
vicajilau Jul 20, 2026
0d2900d
Merge pull request #186 from keinstn/upstream/sftp-close-channel
vicajilau Jul 20, 2026
e09f8db
Support the RFC 8731 name curve25519-sha256
Jul 27, 2026
4a1a10d
Merge pull request #187 from NEXT176-s-r-o/nviper/curve25519-sha256-a…
vicajilau Jul 27, 2026
6213203
feat: prioritize RFC 8731 curve25519-sha256 key exchange in algorithm…
vicajilau Jul 27, 2026
a2de640
fix: export ssh_userauth.dart in public API (#188)
vicajilau Jul 30, 2026
6e37986
style: apply line formatting to SSH userauth export tests
vicajilau Jul 30, 2026
86cdb88
Merge pull request #189 from vicajilau/fix/export-ssh-userauth
vicajilau Jul 30, 2026
144697c
Merge upstream dartssh2 v2.22.5
GT-610 Aug 2, 2026
9880173
fix: address v2.22.5 merge review findings
GT-610 Aug 2, 2026
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
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ version: 2
updates:
- package-ecosystem: "pub"
directory: "/example/"
schedule:
interval: "weekly"
time: "09:00"
timezone: Europe/Madrid

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
time: "09:00"
Expand Down
38 changes: 37 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
## [2.22.5] - 2026-07-30
- Exported `src/ssh_userauth.dart` in `lib/dartssh2.dart` to expose `SSHUserInfoRequest`, `SSHUserInfoPrompt`, `SSHAuthMethod`, and `SSHChangePasswordResponse` [#188]. Thanks [@vicajilau].

## [2.22.4] - 2026-07-27
- Advertised standard RFC 8731 key exchange name `curve25519-sha256` alongside legacy `curve25519-sha256@libssh.org` [#187]. Thanks [@nickn17].

## [2.22.3] - 2026-07-20
- Fixed an SSH channel leak in `SftpClient.close()` by closing the underlying SSH channel and returning `Future<void>` to allow awaiting channel teardown [#186]. Thanks [@keinstn].

## [2.22.2] - 2026-07-15
- Added `flush()` to `SSHSocket`, `SSHClient`, and `SSHChannel` to allow force flushing of buffered outgoing data [#183]. Thanks [@vicajilau].

## [2.22.1] - 2026-07-13
- Fixed a keepalive issue where overlapping pings could occur and caught errors during ping execution. Thanks [@vicajilau].

## [2.22.0] - 2026-07-03
- Added optional `handshakeTimeout` and `authTimeout` to `SSHClient` to limit connection negotiation and user authentication times [#182]. Thanks [@GT-610].

## [2.21.1] - 2026-07-02
- Fixed an `SSHTransport` busy-loop (100% CPU / ANR) that occurred when a partial packet remained in the read buffer [#179]. Thanks [@vicajilau].

## [2.21.0] - 2026-07-01
- Added `SSHSession.waitForExit({Duration? timeout})` to await remote process exit status with an optional timeout [#176]. Thanks [@GT-610].
- Hardened SOCKS5 dynamic forwarding (half-close streaming, dialing guards, timeout cancellation, malformed UTF-8 decoding, and buffer limits) [#175]. Thanks [@GT-610].
- Hardened SSH agent channel frame validation (rejecting empty or oversized frames) and fallback RSA signature type checks [#175]. Thanks [@GT-610].
- Improved EC private key parsing with proper ASN.1 OID curve detection, public point derivation validation, and robust comments decoding [#175]. Thanks [@GT-610].

## [2.20.0] - 2026-06-30
- **BREAKING**: Bumped the minimum Dart SDK constraint to `3.0.0` [#23]. Thanks [@vicajilau].
- **BREAKING**: Declared `OpenSSHKeyPair` as an `abstract mixin class` to comply with Dart 3.0 class modifier rules [#23]. Thanks [@vicajilau].
Expand Down Expand Up @@ -253,7 +280,15 @@
[#18]: https://github.com/TerminalStudio/dartssh2/issues/18
[#17]: https://github.com/TerminalStudio/dartssh2/issues/17
[#14]: https://github.com/TerminalStudio/dartssh2/pull/14
[#175]: https://github.com/TerminalStudio/dartssh2/pull/175
[#176]: https://github.com/TerminalStudio/dartssh2/pull/176
[#179]: https://github.com/TerminalStudio/dartssh2/pull/179
[#182]: https://github.com/TerminalStudio/dartssh2/pull/182
[#183]: https://github.com/TerminalStudio/dartssh2/pull/183
[#186]: https://github.com/TerminalStudio/dartssh2/pull/186
[#187]: https://github.com/TerminalStudio/dartssh2/pull/187
[#1]: https://github.com/TerminalStudio/dartssh/pull/1/files
[#188]: https://github.com/TerminalStudio/dartssh2/issues/188
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[@linhanyu]: https://github.com/linhanyu
[@Migarl]: https://github.com/Migarl
Expand All @@ -270,4 +305,5 @@
[@bradmartin333]: https://github.com/bradmartin333
[@Wackymax]: https://github.com/Wackymax
[@gkc]: https://github.com/gkc
[@vicajilau]: https://github.com/vicajilau
[@vicajilau]: https://github.com/vicajilau
[@GT-610]: https://github.com/GT-610
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ void main() async {

`ident` defaults to `DartSSH_2.0`.

### Configure handshake and authentication timeouts

You can specify optional timeouts for the transport handshake and user authentication:

```dart
void main() async {
final client = SSHClient(
await SSHSocket.connect('localhost', 22),
username: '<username>',
onPasswordRequest: () => '<password>',
handshakeTimeout: const Duration(seconds: 15),
authTimeout: const Duration(seconds: 15),
);
}
```

By default, these parameters are `null` (no timeout is enforced). Without these timeouts, the connection or authentication process could hang indefinitely if the remote server becomes unresponsive.

### Spawn a shell on remote host

```dart
Expand Down Expand Up @@ -277,6 +295,25 @@ void main() async {

Processes killed by signals do not have an exit code, instead they have an exit signal property.

**Waiting for exit status with a timeout**

Alternatively, you can wait for the remote process to report its exit status or exit signal with an optional timeout using `session.waitForExit()`:

```dart
void main() async {
final session = await client.execute('sleep 5');

// Wait for the exit status to be reported (or up to 10 seconds).
final exitCode = await session.waitForExit(timeout: Duration(seconds: 10));

if (exitCode != null) {
print('Process exited with code: $exitCode');
} else {
print('Process timed out or was terminated by a signal');
}
}
```

### Forward connections on local port 8080 to the server

```dart
Expand Down
9 changes: 9 additions & 0 deletions lib/src/algorithm/ssh_kex_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ class SSHKexType extends SSHAlgorithm {
digestFactory: SHA256Digest.new,
);

/// RFC 8731 name for the same algorithm as [x25519]. Servers hardened to a
/// single kex commonly offer only this spelling, and OpenSSH matches names
/// literally; without it the handshake dies with "no matching key exchange
/// method found".
static const x25519Rfc = SSHKexType._(
name: 'curve25519-sha256',
digestFactory: SHA256Digest.new,
);

static const nistp256 = SSHKexType._(
name: 'ecdh-sha2-nistp256',
digestFactory: SHA256Digest.new,
Expand Down
3 changes: 1 addition & 2 deletions lib/src/dynamic_forward_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,8 @@ class _SocksConnection {
return;
}

_buffer.add(chunk);

try {
_buffer.add(chunk);
await _consumeHandshake();
} catch (_) {
await close();
Expand Down
2 changes: 1 addition & 1 deletion lib/src/http/http_date.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ DateTime? parseHttpDate(String input) {
}

return null;
}
}
7 changes: 4 additions & 3 deletions lib/src/kex/kex_nist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ String _getNameByCurve(ECDomainParameters curve) {
late BigInt x;
do {
x = decodeBigIntWithSign(
1,
randomBytes((secretBits + 7) ~/ 8),
) % curve.n;
1,
randomBytes((secretBits + 7) ~/ 8),
) %
curve.n;
} while (x == BigInt.zero);

final c = curve.G * x;
Expand Down
16 changes: 14 additions & 2 deletions lib/src/sftp/sftp_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,24 @@ class SftpClient {
}

/// Close the sftp session.
void close() {
///
/// This also closes the underlying SSH channel that the sftp subsystem runs
/// on. Without this the channel is leaked: every [SSHClient.sftp] call opens
/// a fresh session channel, so an application that opens an sftp session per
/// operation would accumulate open channels on the connection until the
/// server refuses further `CHANNEL_OPEN`s.
Future<void> close() async {
if (_done.isCompleted) return;
final error = SftpAbortError("Connection closed");
for (var waiter in _replyWaiters.values) {
waiter.completeError(SftpAbortError("Connection closed"));
waiter.completeError(error);
}
_replyWaiters.clear();
if (!_handshake.isCompleted) {
_handshake.completeError(error, StackTrace.current);
}
_done.complete();
await _channel.close();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void _closeError(Object error, [StackTrace? stackTrace]) {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/socket/ssh_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ abstract class SSHSocket {
Future<void> close();

void destroy();

/// Force flush any buffered outgoing data.
Future<void> flush() async {}
}
5 changes: 5 additions & 0 deletions lib/src/socket/ssh_socket_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class _SSHNativeSocket implements SSHSocket {
_socket.destroy();
}

@override
Future<void> flush() async {
await _socket.flush();
}

@override
String toString() {
final address = '${_socket.remoteAddress.host}:${_socket.remotePort}';
Expand Down
1 change: 1 addition & 0 deletions lib/src/ssh_algorithm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class SSHAlgorithms {
// Prefer modern KEX first; move legacy SHA-1/group1 variants to the end
// as fallback-only to improve security defaults.
this.kex = const [
SSHKexType.x25519Rfc,
SSHKexType.x25519,
SSHKexType.nistp521,
SSHKexType.nistp384,
Expand Down
Loading