The backend could already authenticate with a password, but only the API could
supply the field — the UI had no way in. It goes in "Advanced SSH" next to
Identity File: the two are answers to the same question, and seeing them together
is what makes the choice obvious. The hint says outright to prefer a key when one
exists.
Three details, each of which breaks something if skipped:
- the field is type="password" and is never populated from the server. GET is
redacted, so any code writing a value into this box can only be writing a
placeholder — and the next save would store that placeholder as the real
password.
- the value is deliberately not trimmed: leading or trailing spaces may be part
of the password.
- it is added to the remoteFields clearing list. Without that, a typed password
persists across forms and the next new host silently inherits it — credentials
from two different machines bleeding together.
Both submit paths are wired: the standalone "add remote host", and the one that
creates a host as part of the remote-case flow. Wiring only one leaves the other
silently key-only. Guard tests pin each of the above (including "must be both
paths" and "must never repopulate").
The remote path always passed `-o BatchMode=yes`, which disables every
interactive prompt, so password-only hosts could never be used. BatchMode is not
an oversight: Codeman launches ssh non-interactively from a service process with
no terminal and nobody watching, and without it ssh hangs on a password prompt
no one will ever answer — a dead pane, which is worse than an error.
So the password goes through sshpass, handed to ssh in the SSHPASS environment
variable: never in argv (same-host users can read /proc) and never in a temp
file. The variable itself is injected into the pane with socket-scoped
`tmux setenv`, the same rule every other secret here follows.
⚠️ One thing measured, and a naive implementation will hit it: BatchMode=yes and
sshpass are mutually exclusive. The former disables the password prompt, and
answering that prompt is exactly how sshpass works, so using both yields
`Permission denied (publickey,password)` — which reads like a wrong password
rather than wrong arguments. With a password we therefore send BatchMode=no plus
NumberOfPasswordPrompts=1, the latter so a wrong password fails immediately
instead of hanging (also measured).
⚠️ The preflight probe runs in the server process, not in the pane, so
`tmux setenv` does not reach it and that path passes the variable through the
child environment instead. A missing sshpass is reported as a named prerequisite
during the probe as well; otherwise it surfaces as a pane dying with
"sshpass: command not found", which reads like a broken host.
Storage and exposure:
- remote-hosts.json now holds a secret, so it is written 0600, and an existing
file is explicitly tightened once (writeFile's mode only applies on create)
- the API always redacts, returning only a `passwordSet` boolean
- updates merge the stored password, because a redacted host posted back carries
no `password` and a straight write would silently erase it; an explicit empty
string still means "clear"
- the schema deliberately does not apply NO_SHELL_META to `password`: a password
legitimately contains `$` and backticks, and unlike the path fields it is never
interpolated into a shell string