Skip to content

No keyboard-interactive authentication: servers that only offer it reject a correct password #150

Description

@eiserv

What

authMethods in internal/uploader/connection.go offers exactly two SSH authentication methods:

methods = append(methods, ssh.PublicKeys(signer))   // if private-key is set
methods = append(methods, ssh.Password(h.password)) // if password is set

ssh.KeyboardInteractive is never offered.

Why it matters

password and keyboard-interactive are two distinct SSH authentication methods. A server can accept one and refuse the other. The configuration that breaks easySFTP is common and not exotic:

PasswordAuthentication no
KbdInteractiveAuthentication yes

sshd built with PAM frequently ends up here, and several managed and shared hosting providers ship it. The user has a correct password, the server accepts that password over ssh and over FileZilla (both of which try keyboard-interactive), and easySFTP fails with ssh: unable to authenticate. From the user's side this looks like "easySFTP does not work with my host", and session.go's isRetryableConnect correctly refuses to retry it, so the failure is immediate and opaque.

For a project whose stated goal is being a serious option for SFTP deploys, and whose audience explicitly includes shared hosting (see docs/providers.md), this is one of the more likely reasons a first run fails.

Suggested direction

Append a keyboard-interactive method whenever a password is configured, answering every prompt with that password. This is what OpenSSH effectively does for the single-prompt case and what most SFTP clients do:

if h.password != "" {
    methods = append(methods, ssh.Password(h.password))
    methods = append(methods, ssh.KeyboardInteractive(
        func(name, instruction string, questions []string, echos []bool) ([]string, error) {
            answers := make([]string, len(questions))
            for i := range answers {
                answers[i] = h.password
            }
            return answers, nil
        }))
}

Points that need a deliberate decision, not just the code:

  • Genuine multi-factor prompts. If the server asks for a TOTP code, answering with the password is wrong and the run fails anyway, just one round-trip later. That is acceptable (unattended CI cannot satisfy an interactive second factor either way), but the failure message should be clear about it rather than saying "authentication failed".
  • Echoing prompts. Answering a prompt whose echos[i] is true (a non-secret question) with the password is arguably wrong. Returning an empty answer for echoed prompts is the safer reading.
  • Whether this should be opt-in. Given the "usability and user choice over enforced opinions" principle in CLAUDE.md and the fact that offering an extra method costs nothing when the server does not advertise it, on-by-default looks right, but that is the call to make in the PR.

Related

Adjacent gaps, deliberately not folded into this issue: SSH agent forwarding and OpenSSH certificate authentication are both unsupported. Neither has the same "correct credentials are rejected" failure signature, so they are lower priority.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions