Public Access
Decide what Lock does to a running shell, and say it
Pressing Lock nulled and disposed the vault view model and touched nothing else. TerminalWorkspace is injected from App.axaml.cs and outlives every lock, so the SSH connection, the pty and the pump all kept running while the window said "Unlock your vault" — and since0500e43collapsed the WebView while locked, that live session was invisible as well as unstopped. CloseSessionAsync was reachable in production only from DisposeAsync, i.e. shutdown. None of this was written down anywhere, so it was neither a policy nor a bug, which is the actual problem. Shells now deliberately outlive the lock, and every layer says so. The reason to prefer this over making Lock a disconnect: locking is what a person does when they walk away from the machine, which is exactly when a long upgrade, build or transfer is most likely to be in flight. Ending every shell would make Lock a button that destroys work, and the predictable response is to stop pressing it and leave the vault open instead. The idle auto-lock this will grow decides it outright — an unattended timeout that killed a running job would be worse than the exposure it removes. Closing the channel also buys less than it looks: the session was authorised at connect time by a credential the remote verified itself, and no vault key participates in keeping it alive, so locking cannot retroactively un-authorise it any more than removing a member can. Stated honestly rather than implied, because the lock screen is what hides it: - The unlock screen shows how many shells are still connected, and that locking closes the vault and not the connections — so a machine still holding authenticated SSH channels does not present itself as merely "locked". Shown only when there is something to disclose. Quitting is what ends them, and the text admits that. - The Lock button carries the same thing in a tooltip, since its name implies the opposite of what it does to a shell. - README lists it as a third architecture consequence beside non-retroactive revocation, which is the same shape of honest limit; docs/crypto.md §10 records it as a threat-model boundary; TerminalWorkspace and LockAsync carry the argument next to the code. LiveSessionCount deliberately does not count dictionary entries. Nothing removes a session when the remote closes the channel by itself — RunSessionAsync only drops the renderer registration — so sessions.Count would report a shell that exited half an hour ago as still running, on the one screen where a user is deciding whether it is safe to walk away. A completed Run task is what "the shell is gone" actually looks like. While locked the number can only fall, since opening a session needs the vault, so a stale value over-reports rather than under-reports. Both new tests fail when the policy is reverted: the count test times out against sessions.Count, and the shell test reports "workspace.LiveSessionCount should be 1 but was 0" when Lock closes sessions. ShellFlowTests also stops building its workspace with a real SshNetConnectionFactory that nothing ever called, which had made the suite's independence from the network a coincidence rather than a property. Verified by hand with a live shell, which nothing had done: a harness mirroring MainWindow.axaml's 340,* grid with a real NativeWebView, the shipped WebAssets, a real sshd in a container, and an ISshShellSession decorator recording every window-change the remote is actually told about. Across lock and unlock, no window-change reached the remote at all, stty size answered 50 118 before and after, the renderer's own buffer came back byte for byte with the wrapped line intact, and the session stayed live throughout. A control run that never hides the WebView behaves identically, so nothing above is startup or idle behaviour. Keystrokes injected while locked reach nothing: twelve of twelve SendInput events accepted with the harness confirmed as the foreground window, no probe character in the remote's output, and a following Ctrl-U answered BEL, so nothing was queued in the line editor either. A hidden WS_CHILD window is not eligible for keyboard focus, which is what makes surviving the lock defensible rather than merely convenient. Correction to a claim made inf80b3d4: terminal.js's guard comment listed "a host that hides the WebView while the vault is locked" among the paths that reach a degenerate fit. It does not. Collapsing the control hides a native child window without resizing it, so the page still reports paneWidth 840 and paneHeight 760 with unchanged cols and rows, no ResizeObserver callback fires and the fit never runs. Establishing that rather than assuming it: the same cycle with MINIMUM_FITTABLE_PIXELS patched to 0 — the guard fully disabled — is equally clean. The guard is still right for minimising and for a splitter dragged to the edge; it is simply not what makes locking safe, and must not be cited as though it were. Recorded, not fixed: - Nothing closes one terminal from the interface, so a user reading "1 shell is still connected" can only act on it by quitting. CloseSessionAsync is tested and correct; VaultViewModel discards the session id it would need. - A session whose remote exits keeps its ISshConnection, and the thread ShellStream parks, until the process ends. - Suspected and seen once: before the harness waited for the window's scale to settle, a DPI settle pushed a 2202x1328 pane for a window 1180 logical units wide and a later re-push reflowed the wrapped line. Three later runs at RenderScaling 1.00 never showed it, so it is filed as a lead, not a finding. - WebView2 fails to initialise with CO_E_SERVER_EXEC_FAILURE when the host executable sits under a very long path. Cost an hour on the harness; relevant to packaging.
This commit is contained in:
@@ -108,6 +108,50 @@ Related and not yet addressed: the conflict log above the terminal is an `ItemsC
|
||||
`ScrollViewer` and no `MaxHeight` on an `Auto` row, so enough conflicts squeeze the terminal row toward
|
||||
nothing.
|
||||
|
||||
**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 cannot be 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. A
|
||||
hidden `WS_CHILD` window is not eligible for keyboard focus, so the lock screen is a real input barrier
|
||||
even though the session behind it is live. That is what makes surviving the lock defensible rather than
|
||||
merely convenient.
|
||||
- **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.
|
||||
|
||||
**Nothing hands the terminal keyboard focus after connecting.** The page calls `term.focus()`, which focuses
|
||||
the textarea inside the document, but Avalonia's focus is still on the Connect button — so the first
|
||||
keystrokes after a successful connect go to the shell's UI, not to the remote shell. Click inside the
|
||||
|
||||
Reference in New Issue
Block a user