SSH architecture overview
Emdash’s SSH implementation provides:- Multiple auth methods: Password, private key, or SSH agent
- Secure credential storage: Passwords and passphrases stored in OS keychain via
keytar - Host key verification: Automatic management of
~/.ssh/known_hosts - Connection pooling: Up to 10 concurrent connections
- Remote PTY streaming: Real-time terminal output to local UI
Authentication methods
Emdash supports three SSH authentication methods:Password authentication
Simplest method — authenticate with username and password.- Password is stored securely in OS keychain
- Service name:
emdash-ssh - Keychain key format:
{connectionId}:password
Private key authentication
Most secure method — authenticate with an SSH key pair.- Supports passphrase-protected keys
- Passphrase stored in OS keychain if provided
- Keychain key format:
{connectionId}:passphrase - Paths starting with
~are expanded to home directory
Emdash supports all standard SSH key types: RSA, Ed25519, ECDSA, DSA.
SSH agent authentication
Use your existing SSH agent for authentication.- Requires
SSH_AUTH_SOCKenvironment variable - Automatically detected from user’s login shell
- Supports 1Password, Secretive, and standard ssh-agent
Setting up SSH step-by-step
Generate SSH keys (if needed)
If you don’t have SSH keys yet, generate a new Ed25519 key pair:Press Enter to accept the default location (
~/.ssh/id_ed25519).Optionally add a passphrase for extra security.Copy public key to remote server
Add your public key to the remote server’s Or manually:
~/.ssh/authorized_keys:Configure SSH agent (optional)
For SSH agent authentication, start the agent and add your key:Verify keys are loaded:
On macOS, add
AddKeysToAgent yes to ~/.ssh/config to persist keys across sessions.Test the connection
Verify SSH works before using it in Emdash:If this fails, fix the connection before proceeding.
Credential storage with keytar
Emdash useskeytar to store SSH credentials securely in the OS keychain.
Implementation details
src/main/services/ssh/SshCredentialService.ts
Storage locations
- macOS: Keychain Access (
emdash-sshservice) - Linux: Secret Service API (GNOME Keyring, KWallet)
- Windows: Credential Vault
Credentials are tied to connection IDs. Deleting a connection also deletes its stored credentials.
Host key verification
Emdash automatically manages SSH host keys in~/.ssh/known_hosts.
How it works
First connection
When connecting to a new host:
- Server presents its host key
- Emdash calculates SHA256 fingerprint
- User is prompted to verify the fingerprint
- On approval, key is added to
~/.ssh/known_hosts
Subsequent connections
On future connections:
- Server presents host key
- Emdash checks against
known_hosts - If key matches, connection proceeds
- If key changed, connection is rejected
Host key service API
src/main/services/ssh/SshHostKeyService.ts
Manually managing known_hosts
To remove a host key manually:Remote PTY and agent execution
When you create a task on an SSH connection, Emdash:- Establishes SSH connection
- Creates a remote worktree at
<project>/.emdash/worktrees/<task-slug>/ - Spawns the agent CLI via
ssh2’s shell API - Streams terminal output to local UI in real-time
Shell wrapping for remote PTYs
Remote commands are wrapped in a login shell for proper environment setup:src/main/services/ssh/SshService.ts
~/.bashrc,~/.profileare sourced~/.ssh/configis available- Git config is loaded
- Agent binaries in
PATHare found
Connection pooling
Emdash maintains a pool of up to 10 concurrent SSH connections.src/main/services/ssh/SshService.ts
Pool behavior
- Connections are reused when possible
- If a connection for a given ID already exists, it’s returned immediately
- In-flight connection attempts are coalesced (no duplicate TCP sockets)
- Warning logged when pool reaches 80% capacity
- Hard limit enforced at 10 connections
Connection lifecycle
Troubleshooting SSH issues
Connection refused
Connection refused
Symptoms:
Connection refused or timeout when connectingSolutions:-
Verify SSH server is running:
- Check firewall rules allow port 22 (or custom port)
-
Test connection from terminal:
Permission denied (publickey)
Permission denied (publickey)
Symptoms: Authentication fails with public keySolutions:
-
Verify public key is in
~/.ssh/authorized_keyson server -
Check permissions on server:
-
Verify correct private key path in Emdash (expand
~manually if needed) -
Check SSH server allows key auth:
SSH agent not found
SSH agent not found
Symptoms:
SSH_AUTH_SOCK not set, agent auth failsSolutions:-
Start SSH agent:
-
Add keys:
- Launch Emdash from terminal (not Finder/Dock)
-
Or use
IdentityAgentin~/.ssh/config:
Host key verification failed
Host key verification failed
Symptoms: Connection blocked due to changed host keySolutions:
-
Remove old key:
- Verify the server wasn’t compromised (ask your admin)
- Reconnect in Emdash to add new key
Keytar errors on Linux
Keytar errors on Linux
Symptoms: Then rebuild native modules:
keytar fails to store credentialsSolutions:Install required packages:PTY spawn fails on remote
PTY spawn fails on remote
Symptoms: Agent doesn’t start on remote machineSolutions:
-
Verify agent is installed on remote:
-
Check agent is in remote
PATH: -
Install agent on remote:
Security considerations
Shell injection protection
All remote commands usequoteShellArg() to escape arguments:
src/main/utils/shellEscape.ts
Environment variable validation
Env var keys are validated against a strict regex:Remote PTY restrictions
- Only allowlisted shell binaries (
bash,sh,zsh) - File access gated by
isPathSafe()checks - No direct shell injection in SFTP operations
Credential isolation
- Each connection gets a unique ID
- Credentials stored per-connection, not globally
- Deleting a connection purges its credentials from keychain
Advanced: SSH config integration
Emdash reads~/.ssh/config for advanced settings:
~/.ssh/config
IdentityAgent— Custom SSH agent socket path (overridesSSH_AUTH_SOCK)
Full SSH config parsing is not yet implemented. Most directives are ignored.
Next steps
- Troubleshooting — Common SSH and PTY issues
- Adding providers — Add new CLI agents
- Linear and Jira integration — Link remote tasks to issues