Repository navigation
fix(msb): route ssh-agent forward through a managed symlink - #564
Conversation
The automatic ssh-agent --vsock route pinned the create-time SSH_AUTH_SOCK path, so a
host reboot (macOS launchd mints a new agent socket path per login) left every existing
sandbox with a dead route and broken SSH commit signing; the only remedy was a recreate.
msb dials the route's host_socket per guest connection and follows symlinks, so acq now
emits a per-sandbox symlink under ${ACQ_STATE_DIR}/msb/ssh-agent/ as the route's host path
and atomically re-points it at the current SSH_AUTH_SOCK on every bridge (re)start
(provision, acq start, running re-attach). acq rm removes the link. Custom
ACQ_FORWARD_HOST_SOCKETS entries keep their canonicalized path. Sandboxes created before
this keep their raw route; the unreachable-agent warning now names the one-time recreate
for them and the host agent for managed routes. A hashed basename keeps the link path
inside sun_path; an unsafe name or a failed link falls back to the raw path.
Amends ADR-0021, updates KNOWN_FAILURE_MODES §35 and BACKEND_GUIDE, adds bats cases
10c22-10c29. Verified live on macOS with msb 0.7.7.
Refs: #501, #413
…llback Review follow-ups for the managed ssh-agent route link (ADR-0021 amendment): - The managed-route unreachable warning claimed the link "was just re-pointed" even when the refresh skipped it because SSH_AUTH_SOCK was unset or not a socket in the invoking shell. The refresh now records why it left the link alone and the warning asks for a re-run from a shell where the agent is exported instead of blaming the host agent. - A sandbox name outside [A-Za-z0-9_-] cannot carry a link; create fell back to the raw route silently and the legacy warning then promised a self-healing recreate. The fallback now warns on stderr at create, and the legacy warning promises self-healing only when the name allows a link. - Guard the atomic replace with [ -L "$tmp" ] so an `ln -s` that copies (MSYS without native symlinks) can never install a dead copy as the route. - Extract the link staging into _acq_msb_ssh_agent_route_path and split the warning into two helpers so no touched function exceeds the 50-line cap. Refs: #564
The managed-link refresh has three outcomes (re-pointed; skipped because SSH_AUTH_SOCK is unset or not a socket; the link could not be replaced) but the warnings only worded two, so a failed link replace sent the user to fix a healthy SSH_AUTH_SOCK, the create-time fallback blamed the sandbox name even when the symlink itself failed, and the raw-route warning cited the name charset when the cause could be a state dir too deep for sun_path. The refresh now records the reason together with its remedy, so the managed-route warning has no branching on cause; the create-time fallback distinguishes an unusable name from a link that could not be created and names the link path; the raw-route warning lists every condition a name and state dir must meet. Adds bats case 10c30 (read-only link dir) covering the third state at both start and create. Refs: #564
|
omg great solution! |
|
AI-assisted review (OpenCode), advisory — needs a human to confirm before it drives a change. Reviewed head Checked the claims that a live run wouldn't catch, by compiling/executing rather than reading. One finding, about the VerifiedThe struct sockaddr_un u; printf("%zu", sizeof(u.sun_path)); // -> 104
The atomic swap is actually atomic. Built a real symlink, pointed a The name charset guard holds. Fuzzed 13 inputs through The cksum fallback doesn't collide in practice. Generated 4000 realistic long sandbox names (the only shape that can reach the hashed branch, since short names never overflow) and hashed each: 0 collisions. A collision would mean two sandboxes sharing one route link, so worth checking rather than assuming.
Tests and static analysis. Three defensive details worth noting because they're the kind that get dropped in review: the Finding: the
|
|
Thanks, confirmed by trial-merging this branch onto |
|
@wz-gsa can I get another review and verdict for this PR? |
wz-gsa
left a comment
There was a problem hiding this comment.
Approving. Head is unchanged at de6b9b33 since my 2026-10-07 review, and you closed the one finding I raised more thoroughly than it asked for.
My finding was that "one-line merge in acq_backend_terminate" undersold the #504 interaction. Your reply did the actual work: trial-merged onto docs/adr-host-authoritative-config (256d069), found exactly one conflict hunk with a stated resolution (keep both cleanup calls), identified the three tests that then fail (10c24, 10c25, 10c30), confirmed 34/34 on the merged tree with 8 one-line seed_host_config msb <name> || true additions, and named the ordering you prefer. That is the measured answer, and it also fixed the 33-vs-34 count I had flagged separately.
So the finding is resolved as an understood sequencing dependency, not a defect in this PR. Nothing here blocks on it: the code on this branch stands on its own, and the follow-up is a rebase you have already scoped.
Carrying forward from my earlier review, for the record — the parts I verified by execution rather than reading, all still valid at this head:
ACQ_MSB_SUN_PATH_MAX=103is correct. Compiled a probe:sizeof(sockaddr_un.sun_path)is 104 on macOS, so 103 usable, and budgeting for macOS over Linux's 108 is the right direction.mv -fover the link really isrename(2)-atomic — tested on a live symlink, no missing intermediate state._acq_msb_ssh_agent_link_path's charset guard holds across 13 adversarial names (../../etc/x,a/b,-dash,a;id,a$(id),.,..,a.b), which matters because the name reachesln/rm -fas an argument.- The cksum fallback does not collide: 4000 realistic long names, 0 collisions.
acq rmcannot unlink the agent socket — the[ -L "$link" ]test atmsb.sh:4098means it only ever removes a symlink.- 34/34 in
115-ssh-agent-forward.bats;shellcheck --severity=warningandbash -nclean onmsb.sh.
Filing a tracking issue for the #504 rebase so the 8 seed additions and the acq_backend_terminate resolution do not get rediscovered by whoever lands the second of the two.
Still unverified, unchanged from before: the live macOS/HVF reboot scenario — route persisted as the link path, re-pointing the link changing guest ssh-add -l without a restart, and the kill-agent/new-socket/acq start recovery. No sandbox-capable host here, so those remain on your reported run. Likewise your "same 8 failures on a clean main export" claim about the full offline suite; I ran only the ssh-agent file.
|
Filed #567 to track the #504 rebase — your measured delta (one Checked for an existing issue first; #501 covers the reboot case this PR fixes and #533 covers ADR-0030 merge semantics, but neither tracked this specific interaction. Also noted in #567: |
Resolve the acq_backend_terminate conflict with ADR-0035 by keeping both cleanup calls: the managed ssh-agent route link removal and the host-authoritative config removal. Seed the host config record in the bridge-restart blocks of tests 10c24, 10c25 and 10c30, which now read ssh-auth-sock from the host config store.
The bridge-restart blocks of 10c24, 10c25 and 10c30 read ssh-auth-sock from the host config store since ADR-0035; seed it like the other bridge tests. This was meant to land with the main merge in cf8c02e but was left unstaged.
|
#504 is merged. I recreated the measured rebase twice and confirmed your recipe exactly: one conflict in I did not push the merge because the repository's hook-triggered full test suite exposed a separate destructive test-isolation bug: nested fixture Your branch remains approved, but is now genuinely conflicted against |
wz-gsa
left a comment
There was a problem hiding this comment.
Approving updated head f0364df6 after merging current main (including #504).
The known integration recipe is present and verified:
acq_backend_terminateretains both_acq_msb_ssh_agent_link_remove "$1"andacq_host_config_remove msb "$1" || true.- Host config is seeded in each affected 10c24/10c25/10c30 bridge-restart block.
test/bats/115-ssh-agent-forward.bats: 34/34 pass on the integrated tree.bash -nand ShellCheck are clean for the touched shell/test files.- The original symlink implementation checks remain valid: macOS 103-byte
sun_pathbudget, atomic link swap, strict sandbox-name charset, safe link-only cleanup, and fallback behavior were exercised in the earlier review.
quickstart#569 tracks a separate local hook/test-isolation defect discovered while attempting the same merge locally. It affects developer hook execution, not this branch's remote content or CI. I did not bypass hooks or push a local merge; the author's branch update now carries the measured integration.
Merge only after the refreshed Bash 3.2 build/check completes green.
|
We should probably cut a release that includes this fix. |
Fixes the reboot case of #501 (follows #413).
Problem
The automatic ssh-agent
--vsockroute pins the create-timeSSH_AUTH_SOCKpath. On macOS thelaunchd agent socket path changes on every login, so after a host reboot every existing msb sandbox
has a dead route:
ssh-add -lin the guest reports "communication with agent failed" and SSH commitsigning fails. Until now the only remedy was
acq rm+ recreate.Change
msb dials the route's
host_socketper guest connection and follows symlinks (verified on msb 0.7.7).acq now emits a per-sandbox symlink,
${ACQ_STATE_DIR}/msb/ssh-agent/<sandbox>.sock, as the route'shost path and atomically re-points it at the current
SSH_AUTH_SOCKon every bridge (re)start:provision,
acq start, and the running re-attach re-drive.acq rmremoves the link.ACQ_FORWARD_HOST_SOCKETSentries keep today's canonicalized behavior.unreachable-agent warning now says "recreate once" for them, and points at the host agent for a
managed route.
sun_path; an unsafe sandbox name or a failedsymlink falls back to the raw path (today's behavior).
Docs: ADR-0021 amendment,
docs/KNOWN_FAILURE_MODES.md§35,docs/BACKEND_GUIDE.md.Verification
test/bats/115-ssh-agent-forward.bats: new cases 10c22–10c30; 34/34 pass.mainat0e4bccb): 732 ok, 9 not ok. The same 9 fail on a cleanexport of
mainin the same environment (TMPDIR/volume-path and attach-line assertions), so they arepre-existing.
--severity=warning) and the pinned markdownlint pass.the link to an empty agent and back changed guest
ssh-add -lwith no restart; afteracq stop,killing the agent, starting a new agent at a different socket path and
acq start, the guest listedthe key and
git commitproduced a good signature with no recreate;acq rmremoved the link.Follow-ups (not in this PR)
acq restorepath only.mainis merged into this branch (cf8c02e). Theacq_backend_terminateconflict keeps both cleanup calls, and the bridge-restart blocks of 10c24, 10c25and 10c30 seed the host-config record.
115-ssh-agent-forward.batspasses 34/34.acq create/acq startstderr hangs because the msb VM process inherits fd 9; tracked infix(acq): piping acq create/start stderr hangs until the sandbox stops #568.
AI-assisted.