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
+21 -3
View File
@@ -36,9 +36,27 @@ Ubuntu on both Wayland and X11, Fedora KDE, and macOS 15. This is why the termin
`ITerminalHost` — that abstraction is what preserves the option to swap backends, and it should
not be collapsed away for convenience.
**SSH.NET's `window-change` on `ShellStream` is unverified.** A terminal that cannot resize is
unusable, so this gates M1's client work. Fall back to `IChannelSession` if it does not work, and
prefer forking and upstreaming a fix over reflection as a long-term answer.
**SSH.NET's `window-change` is verified working** as of 2025.1.0 — resolved, not a flag.
`ShellStream.ChangeWindowSize(columns, rows, width, height)` exists and the remote genuinely
observes it: `PtyAndResizeSpikeTests` reads `stty size` back from a real sshd after resizing, and
repeated resizes each take effect. The `IChannelSession` fallback is not needed. That suite stays
in place as a regression guard, because an upgrade that silently stopped sending the request would
present as wrapped output only after a resize — easy to misattribute to the terminal emulator.
**`ShellStream` does not override `ReadAsync`.** The base `Stream` implementation therefore runs
the blocking `Read` on a thread-pool thread, so every open session parks one thread for as long as
it is idle. Fine for the handful of tabs M1 targets; revisit before advertising many concurrent
sessions, since the fix is either an upstream change or driving `IChannelSession` directly.
**SSH.NET cannot share one connection between `SshClient` and `SftpClient`.** A shell plus SFTP to
the same host means two TCP connections, two authentications and — later — two relay sockets.
Connect SFTP lazily and reuse the cached decrypted credential so the user is not prompted twice.
**Agent forwarding is de-scoped from v1.** It needs an upstream SSH.NET change. A vault-backed
agent of our own plus ProxyJump covers the real use cases.
**The SSH suite pulls `linuxserver/openssh-server` from Docker Hub**, which is rate-limited for
unauthenticated pulls. If CI starts failing on image pulls rather than on tests, that is why.
**MSIX packaging is ruled out, not merely deprioritised.** A packaged app runs WebView2 in an
AppContainer where loopback connections are blocked without a `CheckNetIsolation` exemption. The