- 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
102 lines
5.0 KiB
Markdown
102 lines
5.0 KiB
Markdown
# 2. Approval grants are scoped to a program, not a process
|
|
|
|
Date: 2026-08-27
|
|
|
|
## Status
|
|
|
|
Accepted. Supersedes the grant-scoping rule in `docs/ideas/ssh-agent.md`
|
|
("Bounded approval grants"), which this decision deliberately relaxes.
|
|
|
|
## Context
|
|
|
|
The design specified that an approval grant is "scoped to **one key and one
|
|
live client process**", keyed on the peer PID together with that PID's start
|
|
time and the executable path captured at grant time. PID reuse therefore
|
|
cannot inherit a grant, and a re-`exec` invalidates it.
|
|
|
|
That rule was justified by a usability argument:
|
|
|
|
> Approval strictly per signature is unusable for the workflows this feature
|
|
> exists to serve. A `git rebase` over twenty commits with `gpg.format=ssh` is
|
|
> twenty modal prompts; a fetch followed by a push is two.
|
|
|
|
Live testing against a real vault showed the rule does not achieve that. Git
|
|
does not hold a connection to the agent across commits: it spawns a **fresh
|
|
`ssh-keygen -Y sign` process for every commit it signs**. Every one of those
|
|
has a different PID and a different start time, so a PID-scoped grant never
|
|
matches. Approving "for this process" and then making a second signed commit
|
|
prompted again, and a twenty-commit rebase would prompt twenty times whether a
|
|
grant was taken or not.
|
|
|
|
So the grant, as specified, was close to inert: it helped only a single
|
|
long-lived process making repeated signature requests, which is not a workflow
|
|
this feature was built for. The button existed and did nothing useful.
|
|
|
|
## Decision
|
|
|
|
A grant is scoped to **one key and one program, for one user**: it matches on
|
|
the peer UID, the executable path captured at grant time, and the public key.
|
|
PID and process start time are still captured and shown in the prompt, but no
|
|
longer participate in matching.
|
|
|
|
The approval button says "Approve for this program", not "for this process",
|
|
because that is what it now does.
|
|
|
|
Unchanged:
|
|
|
|
- The peer UID must equal the companion's effective UID. That is the one
|
|
property the companion actually verifies, and it is not relaxed here.
|
|
- The window is still bounded by `sshAgentApprovalWindowSec` (default 120s,
|
|
maximum 900s, `0` disables grants entirely).
|
|
- Grants still live only in the companion's memory, never touch disk, and
|
|
never survive a restart.
|
|
- Every lifecycle event that dropped a grant before still drops it: expiry,
|
|
lock, logout, account change, epoch change, disabling the feature, screen
|
|
lock, suspend, `revoke_grants`, and per-grant revocation.
|
|
- A grant still replaces the prompt, not the final check. Epoch, lock state
|
|
and key identity are rechecked immediately before the signing primitive.
|
|
|
|
## Consequences
|
|
|
|
**What this accepts.** During an open window, *any* process running the same
|
|
executable path, as the same user, can obtain a signature with that key
|
|
without a prompt. Under PID scoping that was limited to one process.
|
|
|
|
**Why that is tolerable here.** The threat model this feature works within
|
|
already states it "does not claim to protect an unlocked desktop from
|
|
arbitrary code already running as the same user". A hostile same-UID process
|
|
that wanted a signature under the old rule could simply execute
|
|
`/usr/bin/ssh-keygen` itself and request one in its own right — it would face
|
|
a prompt, but so would any first request under either rule. The scope change
|
|
does not hand an attacker a capability they could not otherwise reach; it
|
|
removes a distinction that cost the user twenty prompts and bought a boundary
|
|
that a same-UID attacker was never obstructed by.
|
|
|
|
**What genuinely widens.** The window is now shared. If the user approves
|
|
`/usr/bin/ssh-keygen` for two minutes to sign a rebase, a concurrent hostile
|
|
invocation of that same binary during those two minutes signs without asking.
|
|
Under PID scoping it would have prompted. This is a real reduction, and it is
|
|
the price of the feature working at all.
|
|
|
|
**Mitigations retained.** The window defaults to 120 seconds rather than the
|
|
900-second maximum; grants are visible in the panel with their remaining time
|
|
and revocable individually or all at once; and every live grant is destroyed
|
|
by a lock, a screen lock, or a suspend.
|
|
|
|
**If this proves too wide**, the narrower option is to keep program scoping but
|
|
additionally require that the requesting process's parent match the one that
|
|
was approved — which would cover Git's per-commit children while excluding
|
|
unrelated invocations. That was not done here because parent PIDs are as
|
|
forgeable as any other `/proc` metadata and would add a check that reads as a
|
|
security boundary without being one.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Key-only grants for the window.** Simplest, and matches what `ssh-agent`'s
|
|
own confirm timeout does. Rejected as wider than necessary: the program is
|
|
cheap to match on and excludes unrelated binaries.
|
|
- **Keep PID scoping and document the limit.** Honest, but leaves a button in
|
|
the UI that almost never does anything, which is its own kind of dishonesty.
|
|
- **Drop grants from v1.** Removes the machinery, but returns the twenty-prompt
|
|
rebase the design explicitly called unusable.
|