Clear the SSH gate: window-change reaches the remote, and licence as MIT

Licence is MIT, set solution-wide rather than only on the packable project:
DodoSSH.Contracts is published so clients can build against it, and a
package with no licence expression is one a corporate policy scanner
rejects outright.

The SSH.NET spike is the M1 client gate and it passes. SSH.NET 2025.1.0
exposes ShellStream.ChangeWindowSize, but a method existing is not the
remote observing it, so the tests read `stty size` back from a real sshd
after resizing rather than asserting the call did not throw. Repeated
resizes each take effect too, which matters because dragging a window edge
produces a stream of them. The IChannelSession fallback is not needed.

Also verified against a real sshd: password and public-key auth, that the
host key arrives as a raw blob we can fingerprint ourselves rather than
reading SSH.NET's MD5 property, and that refusing the key via CanTrust
actually aborts the connection -- without which the TOFU dialog would be
decoration.

Kept as a permanent suite, not deleted after the spike. An upgrade that
silently stopped sending the request would present as wrapped output only
after a resize, which is easy to misattribute to the terminal emulator.

Two bugs in the test itself, both worth naming because either would have
been read as "resize does not work":

- A PTY emits CRLF, and the anchored regex rejected the CR. The output
  visibly contained `24 80` while the match failed.
- Each read can begin with output still buffered from the previous command,
  including its size line. Taking the first match would have reported the
  pre-resize size.

platform-flags.md now records window-change as resolved rather than
unverified -- a stale flag is worse than none -- plus the three real SSH.NET
limits found on the way: ShellStream does not override ReadAsync so every
idle session parks a pool thread, one connection cannot serve both
SshClient and SftpClient, and agent forwarding needs an upstream change.
This commit is contained in:
2026-07-28 16:52:09 +02:00
parent b7325b78ca
commit 885fb17bdc
15 changed files with 956 additions and 46 deletions
+23 -21
View File
@@ -1429,15 +1429,6 @@
"resolved": "1.0.5",
"contentHash": "LaSDYOJDh2WncgRboqiWtk/Igqoim/LV7v808qBeWY/f36Ol5oEKguEYpKrWw5ap8KYP0SRXf7/v3zil9koY6Q=="
},
"SSH.NET": {
"type": "Transitive",
"resolved": "2025.1.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==",
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3"
}
},
"Stef.Validation": {
"type": "Transitive",
"resolved": "0.3.0",
@@ -1470,18 +1461,6 @@
"System.CodeDom": "6.0.0"
}
},
"Testcontainers": {
"type": "Transitive",
"resolved": "4.13.0",
"contentHash": "j8vi9jPBNSwaraGGx8w+2gtZyWrlbKxdhiGMS3nektg+KiwjFWx9ghCjs57EoQfvI+IAbzti0oQJupQChwgMog==",
"dependencies": {
"Docker.DotNet.Enhanced": "4.3.3",
"Docker.DotNet.Enhanced.X509": "4.3.3",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3",
"SSH.NET": "2025.1.0",
"SharpZipLib": "1.4.2"
}
},
"TinyMapper.Signed": {
"type": "Transitive",
"resolved": "4.0.0",
@@ -1803,6 +1782,29 @@
"dependencies": {
"libsodium": "[1.0.22, 1.0.23)"
}
},
"SSH.NET": {
"type": "CentralTransitive",
"requested": "[2025.1.0, )",
"resolved": "2025.1.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==",
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3"
}
},
"Testcontainers": {
"type": "CentralTransitive",
"requested": "[4.13.0, )",
"resolved": "4.13.0",
"contentHash": "j8vi9jPBNSwaraGGx8w+2gtZyWrlbKxdhiGMS3nektg+KiwjFWx9ghCjs57EoQfvI+IAbzti0oQJupQChwgMog==",
"dependencies": {
"Docker.DotNet.Enhanced": "4.3.3",
"Docker.DotNet.Enhanced.X509": "4.3.3",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3",
"SSH.NET": "2025.1.0",
"SharpZipLib": "1.4.2"
}
}
}
}