feat: per-server SSH agent forwarding (ForwardAgent)#53
Merged
Conversation
Add an opt-in per-server ForwardAgent equivalent so processes on the remote host can authenticate to other SSH hosts using the keys in the local ssh-agent (e.g. git over SSH on a remote server with a local GitHub key), without copying any private key to the server. Requested in #52 by @raphaelbahat. - config-loader: read FORWARD_AGENT (.env) / forward_agent (TOML) into a camelCase forwardAgent field, emit it in exportToToml. A parseBool() helper coerces the .env string so "false" is not treated as truthy. - ssh-manager: set connConfig.agentForward only inside the SSH_AUTH_SOCK block, since ssh2 throws if agentForward is set without a valid agent. ssh2's connection-level allowAgentFwd forwards on every exec/shell channel, so no per-command change is needed. Defaults to false — fully opt-in, no behaviour change for existing configs. Documented with the standard ForwardAgent security warning in README and CLAUDE.md. Tests: test-agent-forward.js drives connect() with a stubbed ssh2 client across the four states (on / off / absent / no-agent), and test-config-field-names.js gains forwardAgent coverage incl. env boolean coercion and a TOML export round-trip. Closes #52
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.
Summary
Implements per-server SSH agent forwarding (the equivalent of OpenSSH's
ForwardAgent yes), as requested in #52 by @raphaelbahat. Processes on the remote host can authenticate to other SSH hosts using the keys in the operator's localssh-agent— e.g.git cloneover SSH on a remote server using a local GitHub key — without copying any private key to the server.Thanks @raphaelbahat for the exceptionally well-researched issue (accurate code citations, correct security note, right implementation location).
Changes
src/config-loader.js— read the new option from both formats into a camelCaseforwardAgentfield, and emit it fromexportToToml():.env:SSH_SERVER_<NAME>_FORWARD_AGENT=trueforward_agent = trueparseBool()helper coerces the value. This matters: in.envevery value is a string, so a naive read would makeFORWARD_AGENT=falsetruthy.parseBooltreats onlytrue/1/yes/onas true;false/absent/empty →false.src/ssh-manager.js— setconnConfig.agentForward = trueonly inside the existingif (process.env.SSH_AUTH_SOCK)block. ssh2 throws ("You must set a valid agent path to allow agent forwarding") ifagentForwardis set without a validagent, so nesting it guarantees that can't happen.Why no per-command change is needed
The MCP tools run commands via
SSHManager.execCommand()→client.exec(cmd, cb)with no per-channel options. I verified against the installed ssh2 1.17.0 (lib/client.js:1221) that the agent-forward channel request fires whenthis.config.allowAgentFwd === trueOR the per-execopts.agentForward === true. The connection-level option (agentForward: true→allowAgentFwd) therefore forwards on everyexec/shellchannel automatically. Setting it once at connect is sufficient.Security
Opt-in, defaults to
false. Documented in README (new SSH Agent Forwarding section) and CLAUDE.md with the standard warning: anyone with root on the remote host can use the forwarded socket to impersonate the operator for the life of the connection — mirroringssh_config(5)'sForwardAgentcaution. No new files, no prompts, no behaviour change for existing configs.Tests / verification
tests/test-agent-forward.js(new,npm run test:agentforward) — drivesconnect()with a stubbed ssh2 client and asserts the wiring across four states: forwarding on (agentForwardset), explicitly off, absent (default off), and the criticalforwardAgent:truebut noSSH_AUTH_SOCKcase where neitheragentnoragentForwardis set (so ssh2 never throws).tests/test-config-field-names.js— extended to coverforwardAgentfor both.envand TOML, the boolean coercion (incl.false→false), and a TOML export→reload round-trip.npm test✅ ·./scripts/validate.sh✅ ·npx knip✅ ·npx eslint src/0 errors.Closes #52