Relationships
#1339 open method fails for password-auth hosts: SSHPASS env set but ssh call never wrapped in sshpass
Opened by kitquiet · 7/22/2026· Shipped 7/23/2026
Description
For a fleet host configured with [HOST-1]: password, the open
method (which establishes the ControlMaster socket) always fails with
Permission denied / Too many authentication failures, even when the
password is correct and the identical credentials work fine via exec,
script, and copy on the same host.
Root cause
In models/_lib/operations.ts, runOpen() builds its argv via
openMasterArgv(bins.ssh, {...}) (from _lib/control_master.ts) and passes
it straight to runHosts() — it never passes the argv through
maybeWrapSshpass(). So for a password-auth host, open spawns the plain
ssh binary directly, never sshpass -e ssh ....
envFor() does still set the SSHPASS env var on the spawned process (via
spawnEnv()), but that's a no-op here: only the sshpass wrapper program
reads SSHPASS; bare ssh does not. With no TTY available to the
Deno.Command-spawned child either, ssh can't get a password from anywhere,
so pubkey (if any key is offered) fails, then the interactive password/
keyboard-interactive prompt goes unanswered, and the SSH server eventually
disconnects with "Too many authentication failures."
By contrast, buildExecArgv() in models/_lib/runner.ts (used by exec,
script, copy) correctly ends with return maybeWrapSshpass(host, argv, ctx); — that's why those three methods work fine with password auth and
open doesn't.
Note: check and close also skip maybeWrapSshpass (checkMasterArgv/
exitMasterArgv in the same file), but that's correct as-is — ssh -O check/ssh -O exit just signal an existing control socket locally and don't
perform a fresh authentication, so no password is ever needed there. The bug
is scoped specifically to open.
Reproduction
swamp extension pull @swamp/ssh- Create a model with one host using
auth.kind: password(password from a vault, e.g.${{ vault.get('vault', 'key') }}), pointed at any real SSH server that accepts password auth. swamp model method run <name> exec --input hosts=all --input command='echo ok'→ succeeds.swamp model method run <name> open --input hosts=all→ fails. The writtenmasterAuditresource shows:"Permission denied, please try again.\r\nReceived disconnect from <host> port 22:2: Too many authentication failures\r\nDisconnected from <host> port 22\r\n"
Verified against the currently-published version.
Suggested fix
In runOpen(), wrap the constructed argv the same way buildExecArgv does:
plans.push({ host, argv: maybeWrapSshpass(host, openMasterArgv(bins.ssh, {...}), ctx), env: envFor(host, args.env) }); (need an ArgvContext in
scope for maybeWrapSshpass, same as buildExecArgv already has).
Impact / workaround
Any fleet with password-auth hosts can't use ControlMaster connection reuse
at all — open/check will always report the master absent/errored. Not
fatal: exec/script/copy re-authenticate per call and work correctly, so
a fleet can just skip open entirely and rely on those. But it silently
defeats the whole point of ControlMaster (connection reuse/speed) for any
password-auth host, and the failure mode (misleading auth-failure text) makes
it look like a wrong password rather than a wrapping bug.
Environment
@swamp/sshversion: 2026.07.02.1 (latest published at time of filing)- swamp CLI version: 20260722.020746.0-sha.5d00afa2
- Host OS: Debian 12 (bookworm)
- OpenSSH: 9.2p1 (client and server)
Upstream repository: https://github.com/swamp-club/swamp-extensions
Environment
- Extension:
@swamp/ssh@2026.07.02.1 - swamp:
20260722.020746.0-sha.5d00afa2 - OS:
linux(x86_64) - Deno:
2.8.3 - Shell:
/bin/bash
Shipped
Click a lifecycle step above to view its details.