Public Access
Merge branch 'claude/gallant-brahmagupta-1f8244'
Writes down that locking the vault leaves shells running, and shows the count on the unlock screen rather than leaving it to be inferred. Conflict resolution: - ShellFlowTests' fixture keeps main's FakeSshConnectionFactory. The branch added an IdleSshConnectionFactory for exactly what main's fake already does — a shell that is open, silent and never closes on its own — so FakeSshConnections.cs is dropped rather than merged, leaving one fake SSH stack in the suite instead of two that would drift apart. - MainWindowViewModel and TerminalWorkspace: both sides added their own members, so both are kept. - TerminalWorkspaceTests was added by both branches, with the renderer gate on one side and session lifetime on the other. Merged into one class over one set of helpers; the gate tests now use FakeConnectionFactory rather than an NSubstitute stub, since the suite already has the fake. gallant's polling Timeout constant is PollTimeout, which no longer reads as the renderer's. - platform-flags.md keeps main's measured focus section and drops the short "nothing hands the terminal keyboard focus" entry the branch still carried, which that section supersedes. One genuine disagreement between the branches, left visible rather than flattened: this branch measured that a collapsed WebView cannot be typed into and attributed it to a hidden WS_CHILD window being ineligible for keyboard focus, while main's focus work measured Win32 focus still held by that hidden window and added a lock path that moves the keyboard off it. Both results stand; the mechanism sentence now defers to the focus entry, which makes the input barrier something the lock path maintains rather than something the platform guarantees. Full suite green, including the container-backed SSH tests.
This commit is contained in:
@@ -499,5 +499,13 @@ server, its operators, its backups and the network. It does **not** address:
|
||||
- metadata — item counts, sizes, timestamps, access patterns and the sharing graph are
|
||||
visible, as are host addresses for relay-enabled hosts;
|
||||
- a weak passphrase — §2 parameters and passphrase entropy are the whole defence;
|
||||
- **a locked vault on a machine with open sessions** — locking zeroes the identity keys, the vault
|
||||
keys and the cache key, so nothing on disk can be read again without the passphrase. It does not
|
||||
touch an SSH channel that is already open: that channel was authorised at connect time by a
|
||||
credential the remote host verified itself, and no vault key participates in keeping it alive.
|
||||
Sessions therefore survive lock **by design** (the client says so on its unlock screen, and the
|
||||
README explains why), which means a locked client can still hold authenticated access to remote
|
||||
hosts. Ending that is quitting the client, or rotating the credential — the same non-retroactive
|
||||
limit as revocation, one layer down;
|
||||
- supply chain — a server can serve a backdoored client. Sign releases with a key the server
|
||||
does not hold. In a self-hosted E2EE product this is the largest practical hole.
|
||||
|
||||
@@ -158,6 +158,53 @@ headless test renders and focuses correctly and would confirm the wrong belief.
|
||||
the plumbing that drives it — that connecting asks for focus once per session, that a failed connect does
|
||||
not, and that locking stops the forwarding.
|
||||
|
||||
**The lock/unlock cycle does not resize the pane at all, and the 40 px guard is not what makes it safe.**
|
||||
Measured on Windows with a live shell, against a real `sshd` in a container, in a harness mirroring
|
||||
`MainWindow.axaml`'s `340,*` grid: with the `NativeWebView` collapsed by `IsVisible=false`, the page still
|
||||
reports `paneWidth: 840, paneHeight: 760`, unchanged `cols`/`rows`, and `visibilityState: "visible"`.
|
||||
Hiding is `SetWindowPos(holder, …, SWP_HIDEWINDOW)`, which does not resize the holder, so no
|
||||
`ResizeObserver` callback fires, no fit runs, and **no `window-change` reaches the remote** — before,
|
||||
during or after the cycle. `stty size` on the remote answered `50 118` both before locking and after
|
||||
unlocking, and the renderer's own buffer came back byte for byte, wrapped lines included.
|
||||
|
||||
The guard's irrelevance here was established rather than assumed: the same run with
|
||||
`MINIMUM_FITTABLE_PIXELS` patched to `0` — the guard fully disabled — produced an identical clean result.
|
||||
So the guard is still worth keeping for the paths it was written for, minimising and a splitter dragged to
|
||||
the edge, but it is **not** on the lock path and must not be cited as the reason locking is safe. It was
|
||||
described that way when it landed.
|
||||
|
||||
Two further results from the same harness, both about the deliberate decision that shells outlive a lock
|
||||
(README, `MainWindowViewModel.LockAsync`):
|
||||
|
||||
- **A collapsed WebView is not typed into.** With the harness confirmed as the foreground window and all
|
||||
twelve injected `SendInput` events accepted, not one character of the probe reached the remote pty, and a
|
||||
`Ctrl-U` afterwards answered `BEL` — nothing was sitting in the remote's line editor either. So the lock
|
||||
screen is a real input barrier even though the session behind it is live, and that is what makes
|
||||
surviving the lock defensible rather than merely convenient. The *mechanism* is not what this run
|
||||
concluded: it read the result as a hidden `WS_CHILD` window being ineligible for keyboard focus, but the
|
||||
focus entry above measured Win32 focus still held by the hidden holder window, and the lock path now
|
||||
moves the keyboard off it deliberately. Take the barrier as measured here and the reason from there —
|
||||
which also means the barrier is something the lock path maintains, not something the platform guarantees.
|
||||
- **The session survives the cycle in the real control, not only in tests.** `LiveSessionCount` was 1
|
||||
before, during and after, and the shell accepted a command again immediately on unlock.
|
||||
|
||||
*Suspected, seen once, not reproduced:* on the first run — before the harness learned to wait for the
|
||||
window's scale to settle — the window opened at 2558x1367 px and the page reported a 2202x1328 pane
|
||||
(312x88 characters) for a window 1180 logical units wide, which looks like physical pixels arriving where
|
||||
CSS pixels were expected. A later re-push to 1177x672 then reflowed the wrapped line and split it in two.
|
||||
Both events straddled a DPI settle rather than the lock, and three later runs at `RenderScaling 1.00`
|
||||
never showed it. If a user reports mangled scrollback after moving the window between displays of
|
||||
different scale, start here.
|
||||
|
||||
**WebView2 will not initialise when the host executable sits under a very long path.**
|
||||
`CreateCoreWebView2Environment` fails with `COMException 0x80080005 CO_E_SERVER_EXEC_FAILURE` ("Server
|
||||
execution failed") and the terminal never appears. Hit while building the harness above: the same binary
|
||||
that failed from a ~230-character directory ran first time from `%TEMP%\h`. The exact threshold was not
|
||||
established and the mechanism is unconfirmed — the user data folder is created beside the executable by
|
||||
default and the browser process is launched with paths derived from it, so `MAX_PATH` is the obvious
|
||||
suspect. Relevant to packaging: an installer that lands under a deep per-user path would break the
|
||||
terminal with an error that names nothing.
|
||||
|
||||
**The Windows app manifest must declare a `supportedOS` list.** Without it the process reports a
|
||||
downlevel Windows version and Avalonia's native control host fails outright — *"Unable to create child
|
||||
window for native control host"* — so the WebView, and therefore the terminal, does not start at all.
|
||||
|
||||
Reference in New Issue
Block a user