fix(ssh-controlmaster): handle read-only ControlPath under bind-mounted ~/.ssh
The devbox bind-mounts ~/.ssh read-only. A user ~/.ssh/config with a per-host ControlPath under it (the CGNAT idiom `ControlPath ~/.ssh/cm/%r@%h:%p`) is unwritable there, so a plain `ssh <host> pwd` exits 255 trying to bind the master socket — blocking `pi --ssh <host>` with "Could not resolve remote pwd". A system default cannot override a user's per-host value (SSH first-value-wins), so this must be handled in the extension. - controlPathWritable(): expands ~, tests whether the socket's parent dir is writable (or missing but creatable via nearest existing ancestor). Pure fs check — OS-agnostic, no host-OS detection. - negotiateMaster / negotiateMasterWithPassword: reuse the system master only when its ControlPath is writable; otherwise start our own /tmp master whose command-line `-o ControlPath` overrides the user's unwritable path. - Remote pwd probe: `-o ControlPath=none -o ControlMaster=no` so a read-only system ControlPath cannot make the initial probe fail. No behaviour change for configs without ControlMaster. Updates README.md + AGENTS.md to match.
This commit is contained in:
@@ -98,11 +98,22 @@ on a remote machine via SSH when `--ssh user@host` is passed.
|
||||
- **ControlMaster negotiation via `ssh -G`.**
|
||||
Before starting any connection, `readSshConfig(remote)` runs `ssh -G <host>`
|
||||
and inspects the `controlmaster` and `controlpath` fields. If the effective
|
||||
config already has `ControlMaster auto` or `yes`, the system socket is reused
|
||||
(`ownsmaster: false`). Otherwise, the extension starts its own master at
|
||||
`/tmp/pi-cm-<pid>.sock` (`ownsmaster: true`). The `session_shutdown` handler
|
||||
only calls `ssh -O exit` when `ownsmaster` is true — it never tears down a
|
||||
connection it didn't create.
|
||||
config already has `ControlMaster auto` or `yes` **and the `ControlPath`
|
||||
directory is writable** (`controlPathWritable()` expands `~`, then checks the
|
||||
socket's parent dir exists+writable, or is missing but creatable), the system
|
||||
socket is reused (`ownsmaster: false`). Otherwise — no system master, **or** a
|
||||
system `ControlPath` on a read-only mount (the devbox bind-mounts `~/.ssh`
|
||||
read-only, so a user's `ControlPath ~/.ssh/cm/...` is unwritable) — the
|
||||
extension starts its own master at `/tmp/pi-cm-<pid>.sock` (`ownsmaster:
|
||||
true`); its command-line `-o ControlPath` overrides the user's unwritable
|
||||
path. The `session_shutdown` handler only calls `ssh -O exit` when
|
||||
`ownsmaster` is true — it never tears down a connection it didn't create.
|
||||
|
||||
- **Read-only-`~/.ssh` safe pwd probe.**
|
||||
The remote working dir is resolved with `ssh -o ControlPath=none -o
|
||||
ControlMaster=no <remote> pwd` — a direct connection that ignores multiplexing
|
||||
so a read-only system `ControlPath` (which a plain `ssh … pwd` would try, and
|
||||
fail, to bind a master socket into) cannot make the initial probe exit 255.
|
||||
|
||||
- **Password auth via `--ssh-ask-pass`.**
|
||||
When the flag is set, `ctx.ui.input()` prompts for a password before
|
||||
@@ -119,9 +130,11 @@ on a remote machine via SSH when `--ssh user@host` is passed.
|
||||
when the paths diverge.
|
||||
|
||||
- **`ownsmaster` vs system master and `--ssh-ask-pass`.**
|
||||
If the system already has a ControlMaster configured for the target host,
|
||||
`--ssh-ask-pass` is silently ignored — the system master handles auth
|
||||
independently and the socket is just reused.
|
||||
If the system already has a ControlMaster configured for the target host
|
||||
*and its `ControlPath` is writable*, `--ssh-ask-pass` is silently ignored —
|
||||
the system master handles auth independently and the socket is just reused.
|
||||
When the system `ControlPath` is unwritable (read-only `~/.ssh`) we start our
|
||||
own master instead, so the password flag is honored on that path.
|
||||
|
||||
### `confirm-destructive.ts`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user