Skip to main content
← Back to list
01Issue
BugOpenExtensions
AssigneesNone

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

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

  1. swamp extension pull @swamp/ssh
  2. 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.
  3. swamp model method run <name> exec --input hosts=all --input command='echo ok' → succeeds.
  4. swamp model method run <name> open --input hosts=all → fails. The written masterAudit resource 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/ssh version: 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
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

7/22/2026, 12:20:53 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.