Skip to content

feat(agent): per-connection ssh cache contexts, per-app no-fallback, pwd-scoped cache keys#3

Merged
timqi merged 1 commit into
mainfrom
agent-cache-context
Jul 14, 2026
Merged

feat(agent): per-connection ssh cache contexts, per-app no-fallback, pwd-scoped cache keys#3
timqi merged 1 commit into
mainfrom
agent-cache-context

Conversation

@timqi

@timqi timqi commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Three auth-cache context fixes for vt ssh agent (design reviewed with codex-expert before and after implementation):

  1. Forwarded-agent narrowing — when the socket peer is the OpenSSH client (basename ssh), every cache mode (incl. global) anchors the context on that ssh process (pid, start_tvsec) instead of the terminal session / app ancestor / shared global slot. A remote host over ssh -A can reuse its own approvals within the TTL, but can no longer ride grants issued by other local tabs or other remote hosts; the context dies with the connection. The per-session/per-app TTY gate runs before the ssh branch, so a TTY-less binary renamed ssh cannot bypass it.
  2. per-app: no peer-pid fallbackfind_app_pid returns Option; no .app/Contents/ ancestor (tmux panes, ssh logins) now means uncacheable instead of keying on the short-lived peer pid, which could never hit again but inserted a dead cache entry per call.
  3. pwd-scoped cache keys — both caches fold the client-reported pwd into the key as an advisory narrower, mirroring the Worker DEK cache design: decrypt key = SHA256(type‖salt‖host‖pwd) (domain tag bumped to v2), sign cache keys on SHA256(fingerprint‖pwd). Plain agent-protocol signs carry no ClientMeta → empty pwd, behavior unchanged; global-mode grants are now scoped to one project tree.

Known tradeoff

Repeated one-shot ssh host invocations no longer share a sign grant within the TTL — the agent cannot distinguish ssh authenticating itself from ssh relaying a forwarded request (same peer process). ControlMaster-multiplexed connections still share one context (one long-lived master process). Documented in CLAUDE.md.

Test plan

  • cargo test (226 passed; new unit tests for is_ssh_client_path, sign_cache_key, pwd partition + field-shift boundaries)
  • cargo check --target x86_64-unknown-linux-gnu (via zigbuild; agent module is macOS-gated)
  • CI

🤖 Generated with Claude Code

…pwd-scoped cache keys

Three auth-cache context fixes:

1. Forwarded-agent narrowing: when the socket peer is the OpenSSH client,
   every cache mode (incl. global) anchors the context on that ssh process
   (pid, start_tvsec) instead of the terminal session / app ancestor /
   shared global slot. A remote host over `ssh -A` can reuse its own
   approvals within the TTL but can no longer ride grants from local tabs
   or other remote hosts; the context dies with the connection. The
   per-session/per-app TTY gate runs before the ssh branch so a TTY-less
   binary renamed `ssh` cannot bypass it.

2. per-app: find_app_pid returns Option; no .app ancestor (tmux panes,
   ssh logins) now means uncacheable instead of keying on the short-lived
   peer pid, which could never hit again but inserted a dead entry per call.

3. Cache keys fold in the client-reported pwd as an advisory narrower
   (mirrors the Worker DEK cache): decrypt key = SHA256(type‖salt‖host‖pwd)
   (domain tag bumped to v2), sign cache now keys on SHA256(fp‖pwd).
   Plain agent-protocol signs carry no ClientMeta and key on empty pwd
   (unchanged behavior); global-mode grants are now scoped per project tree.

Known tradeoff: repeated one-shot `ssh host` invocations no longer share a
sign grant within the TTL (same peer process for self-auth and forwarding —
indistinguishable); ControlMaster-multiplexed connections still share one.
@timqi
timqi merged commit 52ab78e into main Jul 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant