Repository navigation
feat(ssh): support SSH agent authentication - #457
Merged
tianzhou merged 1 commit intoOct 9, 2026
Merged
Conversation
Offer an SSH agent (ssh-agent, 1Password, etc.) to every host in the tunnel chain, after any password or key. The socket comes from the ssh_agent TOML field or --ssh-agent, falling back to SSH_AUTH_SOCK, and makes ssh_password/ssh_key optional. A configured socket that does not exist is an error; a stale SSH_AUTH_SOCK is ignored. A key picked up from ~/.ssh/config that ssh2 cannot parse is skipped when a password or agent is available, while an explicitly configured key still fails. Closes bytebase#456
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Input-validation defects, incomplete public-key fallback, and Windows-specific test failures remain unresolved.
4 open findings
What changed in this PR
Adds SSH agent authentication for direct and proxy-jump SSH tunnels.
Changes:
- Supports configured or ambient SSH agent sockets.
- Skips unusable discovered keys when fallback authentication exists.
- Adds configuration validation, tests, and documentation.
| File | Description |
|---|---|
src/utils/ssh-tunnel.ts |
Resolves agents and applies authentication fallback. |
src/utils/ssh-config-parser.ts |
Marks discovered private keys. |
src/utils/__tests__/ssh-tunnel.test.ts |
Tests agent authentication and fallback. |
src/utils/__tests__/ssh-config-parser.test.ts |
Tests discovered-key metadata. |
src/types/ssh.ts |
Extends tunnel configuration types. |
src/types/config.ts |
Adds source agent configuration. |
src/connectors/manager.ts |
Passes agent settings into tunnels. |
src/connectors/__tests__/manager.test.ts |
Tests manager agent propagation. |
src/config/toml-loader.ts |
Validates and expands agent paths. |
src/config/env.ts |
Adds --ssh-agent handling. |
src/config/__tests__/toml-loader.test.ts |
Tests TOML agent configuration. |
src/config/__tests__/ssh-config-integration.test.ts |
Tests CLI and environment resolution. |
server.json |
Declares SSH_AUTH_SOCK. |
docs/config/toml.mdx |
Documents TOML agent support. |
docs/config/command-line.mdx |
Documents CLI agent support. |
dbhub.toml.example |
Adds an agent example. |
CLAUDE.md |
Updates development guidance. |
.env.example |
Documents ambient agent configuration. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+647
to
+654
| if (args["ssh-agent"]) { | ||
| config.agent = args["ssh-agent"]; | ||
| // Expand ~ to home directory | ||
| if (config.agent.startsWith("~/")) { | ||
| config.agent = path.join(process.env.HOME || "", config.agent.substring(2)); | ||
| } | ||
| sources.push("ssh-agent from command line"); | ||
| } |
Comment on lines
+881
to
+883
| if (processed.ssh_agent) { | ||
| processed.ssh_agent = expandHomeDir(processed.ssh_agent); | ||
| } |
| expect(connectCalls[0].agent).toBe(configuredSock); | ||
| }); | ||
|
|
||
| it('should reject a configured agent socket that does not exist', async () => { |
Comment on lines
+252
to
264
| const canSkip = auth.privateKeyDiscovered && Boolean(password || agent); | ||
| const parsedKey = canSkip ? ssh2.utils.parseKey(privateKey, passphrase) : undefined; | ||
| if (parsedKey instanceof Error) { | ||
| const desc = label || `${hostInfo.host}:${hostInfo.port}`; | ||
| console.warn( | ||
| `Skipping unusable SSH private key from ~/.ssh/config for ${desc} (${parsedKey.message}).` | ||
| ); | ||
| } else { | ||
| sshConfig.privateKey = privateKey; | ||
| if (passphrase) { | ||
| sshConfig.passphrase = passphrase; | ||
| } | ||
| } |
tianzhou
approved these changes
Oct 9, 2026
tianzhou
left a comment
Member
There was a problem hiding this comment.
Overall LGTM. Thanks for the contribution.
I will make some minor cleanup in a followup PR.
tianzhou
added a commit
that referenced
this pull request
Oct 9, 2026
Follow-up to #457: - Reject a bare `--ssh-agent` via requireFlagValue instead of treating it as the literal path "true". - Only tilde-expand a string `ssh_agent` in TOML so a non-string value gets the "invalid ssh_agent" validation error rather than a TypeError. - Treat a discovered key with no private material (an IdentityFile pointing at a .pub file, as 1Password setups do) as unusable, so it no longer blocks password/agent fallback. Mirrors the checks ssh2 runs in connect(). - Skip the Unix-only missing-socket tests on Windows. Claude-Session: https://claude.ai/code/session_016RxaApSn5G62Ka7HTunaxN Co-authored-by: Claude <[email protected]>
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.

Closes #456
Summary
Lets SSH tunnels authenticate through an SSH agent (ssh-agent, the 1Password SSH agent, etc.), so sources no longer need
ssh_passwordorssh_keywhen the key lives in an agent.Behaviour
ssh_agent(TOML, per source) or--ssh-agent, then theSSH_AUTH_SOCKenvironment variable.~/is expanded for the TOML field and the flag.ssh_proxy_jumphops, after any password or key. This is ssh2's default order; configs that work today are unaffected.SSH agent socket not found: <path>. A staleSSH_AUTH_SOCKis ignored with a warning and does not count as an auth method. The existence check is skipped on Windows, where agents are named pipes or Pageant.~/.ssh/config: ssh2 throws on a key it cannot parse (e.g. encrypted, no passphrase) before trying other methods. A key picked up from~/.ssh/config(IdentityFileor a default~/.ssh/id_*) is now skipped with a warning when a password or agent is available. A key given explicitly (ssh_key,--ssh-key,SSH_KEY) is never skipped and still fails with ssh2's parse error.Not included
IdentityAgentandIdentitiesOnlyfrom~/.ssh/configare not read.SSH_AUTH_SOCK.Both are raised as design questions in #456; happy to adjust either way.
Implementation notes
SSHTunnelConfiggainsagentandprivateKeyDiscovered.parseSSHConfigsets the latter for any key it resolves; a jump host's own key is always treated as discovered.SourceConfig.ssh_key_discoveredis an internal marker that carries that flag through the single-DSN (--ssh-hostalias) path. It is stripped from TOML input.connectToHostnow takes oneSSHHostAuthobject instead of separate password/key/passphrase arguments.utils.parseKeyis read off ssh2's default export, becauseutilsis not exposed as a named export of the CommonJS module under ESM.Testing
ssh_agentoverSSH_AUTH_SOCK, jump hosts, missing and stale sockets, the discovered-versus-explicit key skip,--ssh-agent, and TOML validation and~expansion.pnpm test:unitpasses.SSH_AUTH_SOCK, on an earlier revision of this branch (beforessh_agent/--ssh-agentand the key-skip change were added).~/.ssh/configfalls back to the agent, an explicit one still errors, and a missing configured socket is reported.Docs
docs/config/command-line.mdx,docs/config/toml.mdx,dbhub.toml.example,.env.example,server.jsonandCLAUDE.md.