How DBX Handles SSH Tunnel Connections with Password Authentication
TLDR: DBX implements SSH password authentication by retrieving encrypted credentials from its secret store, constructing a transport-layer description in TypeScript, and executing asynchronous password authentication in Rust using session.authenticate_password(), with explicit timeout handling and clear error messages for credential failures.
DBX is an open-source database client designed to simplify secure connections to remote infrastructure. When key-based authentication is unavailable, DBX supports SSH tunnel connections with password authentication through a secure, multi-layered architecture spanning its TypeScript frontend and Rust core.
Retrieving Passwords from Secure Storage
DBX never stores SSH passwords in plain text. Instead, the system looks up secrets using the key pattern ssh_tunnels.<hop-id>.password or the legacy top-level key ssh_password. According to the TypeScript builder in packages/node-core/src/connections.ts (lines 191-194), this retrieval happens before the connection is established.
The retrieved string is stored in the SshTunnelConfig.password field, defined in crates/dbx-core/src/models/connection.rs. This field is deserialized from the connection JSON and kept in memory only, never written to logs or the UI.
Building the Transport Layer
When a connection profile has ssh_enabled: true and includes one or more ssh_tunnels, DBX constructs a layer description array. In packages/node-core/src/connections.ts (lines 156-157), the TypeScript builder creates objects with type: "ssh" for each hop:
if (config.ssh_enabled && Array.isArray(config.ssh_tunnels) && config.ssh_tunnels.length > 0) {
// Each hop becomes a layer of type "ssh"
layers.push(...config.ssh_tunnels.map((hop) => ({
type: "ssh" as const,
...hop,
})));
}
This layered approach allows DBX to chain multiple SSH hops if needed.
Authenticating with the SSH Server
The actual tunnel implementation resides in crates/dbx-core/src/db/ssh_tunnel.rs. When DBX spawns an async SshTunnel object, it checks the auth_method field. If empty or explicitly set to "password", DBX executes password authentication using the ssh_password credential retrieved earlier:
if auth_method.is_empty() && !ssh_password.is_empty() || auth_method == "password" {
let auth_res = tokio::time::timeout(
connect_timeout,
session.authenticate_password(ssh_user, ssh_password)
)
.await
.map_err(|_| "SSH password authentication timed out".to_string())?;
if !auth_res {
return Err("SSH password authentication failed".to_string()); // line 144
}
}
The tokio::time::timeout wrapper ensures the connection attempt respects the configured connect_timeout, preventing indefinite hangs on unresponsive servers.
Configuration Example
To configure password authentication for an SSH tunnel, include the auth_method and password fields in your connection profile:
{
"id": "my-postgres-db",
"driver": "postgres",
"host": "127.0.0.1",
"port": 5432,
"username": "dbuser",
"password": "dbsecret",
"ssh_enabled": true,
"ssh_tunnels": [
{
"id": "bastion-1",
"host": "bastion.example.com",
"port": 22,
"username": "sshuser",
"auth_method": "password",
"password": "my-ssh-password"
}
]
}
The UI automatically encrypts and stores the "password" field in DBX's secret store (SQLite/SQLCipher), ensuring the plain text never appears on disk.
Error Handling and Security
DBX provides clear error propagation for authentication failures. If the server rejects the password, the Rust core returns "SSH password authentication failed" (line 144 in crates/dbx-core/src/db/ssh_tunnel.rs). If the connection times out, users receive "SSH password authentication timed out".
Passwords remain encrypted in DBX's secret storage and only travel in memory between the TypeScript frontend and Rust core. This design allows seamless fallback to key-based authentication when configured, without requiring external SSH tooling.
Summary
- Secure retrieval: DBX looks up SSH passwords using the
ssh_tunnels.<hop-id>.passwordkey pattern or legacyssh_password, storing them encrypted in SQLite/SQLCipher. - Transport layering: The TypeScript builder in
packages/node-core/src/connections.tsconstructs SSH layer descriptions for each hop defined inssh_tunnels. - Rust authentication: The
SshTunnelimplementation incrates/dbx-core/src/db/ssh_tunnel.rscallssession.authenticate_password()with timeout protection. - Clear error messages: Authentication failures and timeouts return explicit error strings to the UI for user feedback.
- Memory-safe handling: Credentials are never written to logs or disk in plain text, existing only in memory during the connection attempt.
Frequently Asked Questions
Where does DBX store SSH passwords?
DBX stores SSH passwords in its encrypted secret store using SQLite/SQLCipher. Passwords are retrieved using the key pattern ssh_tunnels.<hop-id>.password or the legacy ssh_password key, and they only exist in memory during the connection process, never appearing in logs or configuration files on disk.
What happens if SSH password authentication times out?
If the SSH server does not respond to the authentication attempt within the configured connect_timeout period, DBX raises a "SSH password authentication timed out" error. This timeout is enforced by tokio::time::timeout in crates/dbx-core/src/db/ssh_tunnel.rs, preventing the application from hanging indefinitely on unresponsive servers.
Can I mix password and key-based authentication for different tunnels?
Yes. DBX determines the authentication method per tunnel hop by checking the auth_method field. You can set "password" for one hop and use key-based authentication (by omitting the password and configuring SSH keys) for another, allowing flexible configurations for complex network topologies with multiple bastion hosts.
How does DBX handle legacy SSH password configurations?
DBX checks both the modern ssh_tunnels.<hop-id>.password path and the legacy ssh_password field when deserializing the connection configuration in crates/dbx-core/src/models/connection.rs. This ensures existing configurations continue to function while allowing users to migrate to the newer hierarchical format that supports multiple hops.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →