feat: authenticate key-based hosts through an ssh-agent - #36
Merged
Merged
Conversation
When keyFile is set, key-based authentication now goes through an
ssh-agent by default instead of screen-scraping the passphrase prompt
and replaying it on every master-connection rebuild. The passphrase is
consumed once to ssh-add the key; subsequent connections and every
ControlPersist rebuild then authenticate non-interactively, so the
wrapper no longer needs a replayable secret.
Agent acquisition per host, at connect time:
1. use hostInfo.identityAgent if set;
2. else ssh-add the key (with a -t lifetime) into $SSH_AUTH_SOCK;
3. else, on POSIX, spawn one shared per-user ssh-agent at a fixed
socket (SSH_CLIENT_WRAPPER_AGENT_DIR / $XDG_RUNTIME_DIR /
/run/user/<uid> / /tmp) and use that.
On any failure - or when useAgent is false, or on win32 with no
reachable agent - it silently falls back to the legacy pty path.
New hostInfo options: useAgent (default true when keyFile set),
identityAgent, agentKeyTTL (default 3600). New method: dispose()
(ssh-add -d this host's key, then disconnect; never kills the shared
agent). New hook: hostInfo.reauthRequired (unused by current consumers).
lib/index.d.ts: added the new options/method plus previously-missing
ControlPersistDir and remoteToRemoteCopy, and widened the password /
passphrase callback return type to allow a Promise.
BREAKING CHANGE: hosts configured with a keyFile now authenticate via
an ssh-agent by default. The passphrase callback is invoked once by
ssh-add rather than once per connection, and a per-user ssh-agent
process may be spawned and left running. Set hostInfo.useAgent = false
to restore the previous pty passphrase-replay behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAC2AN9SHN1KYSt5b8G7zc
lib: - use node: prefix on builtin imports (agent.js) - extract resolveAgentCandidate() / effectiveTtl() to cut ensureAgentForHost() cognitive complexity below the threshold - optional chaining in probeAgent(); single Array#push in resolveWellKnownDir(); String.raw for the identityAgent schema regex - hoist the default deps object out of the constructor default param test: - node: prefix on builtin imports - replace /tmp string literals (flagged as publicly-writable-dir use) with /run paths in stub-only assertions - drop the ssh-add -l cross-check in the integration test (avoided a PATH-resolved binary call); managedAgentSock assertion is enough - socketIsTrusted negative test uses a group-readable (0o640) file instead of a world-writable one Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAC2AN9SHN1KYSt5b8G7zc
|
|
🎉 This PR is included in version 3.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
For key-based auth, authentication now goes through an ssh-agent by default instead of screen-scraping the
Enter passphrase for keyprompt and replaying the passphrase on every master-connection rebuild. The passphrase is consumed once tossh-addthe key; subsequent connections and everyControlPersistrebuild authenticate non-interactively, so the wrapper no longer holds a replayable secret.Password auth is unchanged.
Agent acquisition (per host, at connect time)
hostInfo.identityAgentif set;ssh-addthe key (with a-tlifetime) into$SSH_AUTH_SOCK;ssh-agentat a fixed socket (SSH_CLIENT_WRAPPER_AGENT_DIR/$XDG_RUNTIME_DIR//run/user/<uid>//tmp) and use that.On any failure — or
useAgent: false, or win32 with no reachable agent — it silently falls back to the legacy pty path.API
hostInfooptions:useAgent(defaulttruewhenkeyFileset),identityAgent,agentKeyTTL(default3600).dispose():ssh-add -dthis host's key, thendisconnect(); never kills the shared agent.hostInfo.reauthRequired(unused by current consumers).lib/index.d.ts: added the above plus previously-missingControlPersistDir/remoteToRemoteCopy; widened thepassword/passphrasecallback return type to allow aPromise.Tests
test/agent.js(24 unit tests, fake pty / stubbed child_process).test/sanityCheck.js,test/getSshOption.js,test/interface.js.test/sshExec.js(the "passphrase consumed once" case self-skips unlessTEST_PHis set).npm run lintclean; 108 unit tests pass locally; agent paths verified end-to-end against a realssh-agent.BREAKING CHANGE
Hosts configured with a
keyFilenow authenticate via an ssh-agent by default. The passphrase callback is invoked once byssh-addrather than once per connection, and a per-userssh-agentprocess may be spawned and left running. SethostInfo.useAgent = falseto restore the previous pty passphrase-replay behavior.🤖 Generated with Claude Code