Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

4.3 KiB

3. A signing request gives the user two minutes, not thirty seconds

Date: 2026-08-27

Status

Accepted. Adjusts the request-deadline figure in docs/ideas/ssh-agent.md ("Panel-to-Companion Contract"), which specified thirty seconds.

Context

The control contract said:

Reject overflow, give each request a 30-second deadline, and cancel it when its client disconnects.

Thirty seconds is ample for a machine and short for a person. A signing request has to travel further than the socket: the panel opens or takes focus, the user notices it, reads a key name and a SHA256: fingerprint, considers which program is asking, and decides. During live testing against a real vault that budget expired twice under a user who was doing nothing more unusual than reading the prompt he had been asked to read. Both expiries were recorded as refusals, which then fed the denial cooldown and suppressed further prompts — so a deadline that was merely tight cascaded into signing being disabled.

A second problem was found at the same time and is the more serious of the two. The socket server bounded its wait for the state loop's answer with the same CLIENT_IO_TIMEOUT it used for reading a frame and writing a reply:

let bytes = match timeout(CLIENT_IO_TIMEOUT, response).await { .. };

Both were thirty seconds, so the two clocks expired together by coincidence rather than by design. Raising only the approval deadline would have left the client giving up first and the new deadline doing nothing — the human bound would have been decorative. The two values were coupled without ever being related.

Decision

Three separate bounds, each sized for what it actually waits on.

Bound Value Waits on
approvals::REQUEST_LIFETIME_MS 120s a person deciding
server::RESPONSE_TIMEOUT 150s the state loop's answer, on behalf of a blocked client
server::CLIENT_IO_TIMEOUT 30s a socket read or write

RESPONSE_TIMEOUT deliberately exceeds REQUEST_LIFETIME_MS, so the companion's deadline is always what fires first and there is one authority on when a request is over. A test asserts that ordering, and asserts the literal 120s figure so that changing it stays a deliberate act rather than a side effect.

The held-request deadline used while a vault unlock is pending is derived from REQUEST_LIFETIME_MS rather than repeated, because unlocking asks more of the user than approving does and certainly needs no less time.

Consequences

A blocked client may now wait up to two minutes. In practice it will not: the mechanism that actually reclaims a request promptly is the client disconnect, which the server watches for while a request is pending. Pressing Ctrl-C on a git push ends the request immediately, and the panel's prompt is withdrawn with it. The deadline is the backstop for a client that neither answers nor leaves.

The four-request bound is unchanged, so at most four requests can be waiting at once regardless of how long each may wait. A same-UID process can still occupy those slots with junk requests and delay a legitimate one; that was already inside the threat model this feature does not defend against, and a longer deadline widens the window without changing the conclusion.

Two minutes is still a deadline. A request that nobody answers is refused, the client is told, and the prompt comes down. Removing the bound entirely would leave prompts and blocked clients accumulating with nothing to clear them.

Alternatives considered

  • Keep 30s and make the panel more attention-grabbing. Rejected: the design explicitly forbids desktop notifications in v1, and the remaining levers (opening and focusing the panel) are already used.
  • Restart the clock when the prompt is first displayed. Fairer in principle, since the wait should start when the user could first act. It needs the panel to report display state back to the companion, which adds a control message and a way for a wrong answer to extend a deadline. Not worth the surface for the benefit.
  • Make it configurable. Another setting for something almost nobody would tune, and a badly chosen value degrades either usability or the bound. The figure is documented here instead.
  • Remove the deadline while the panel is open and focused. Attractive, but it makes the bound depend on window state the companion cannot verify.