Skip to content

Conversation

@isEmmanuelOlowe
Copy link

Implements full SSH integration for remote development with security hardening:

Features:

  • SSH connection management (agent/key auth)
  • Automatic ~/.ssh/config detection and import
  • Intelligent auth fallback (agent → key)
  • Remote git operations (clone, worktree, commit)
  • Remote PTY sessions with proper cleanup
  • Remote file system operations
  • "Open In" menu support for remote projects
  • Database persistence for SSH connections

Security:

  • Command injection prevention using POSIX shell escaping
  • Path traversal protection with restricted path validation
  • Secure credential storage via system keychain
  • Environment variable injection protection

Architecture:

  • New services: SshService, RemoteGitService, RemotePtyService
  • Type-safe IPC layer with comprehensive error handling
  • React UI components with hooks for SSH management
  • Database migration for ssh_connections table

Testing:

  • TypeScript compilation: PASSED
  • ESLint validation: PASSED
  • Production build: PASSED
  • Security review: 3 critical issues fixed

Breaking Changes: None (all changes are additive)

Files changed: 33 modified, ~40 new (~1,579 additions, ~75 deletions)

Implements full SSH integration for remote development with security hardening:

Features:
- SSH connection management (agent/key auth)
- Automatic ~/.ssh/config detection and import
- Intelligent auth fallback (agent → key)
- Remote git operations (clone, worktree, commit)
- Remote PTY sessions with proper cleanup
- Remote file system operations
- "Open In" menu support for remote projects
- Database persistence for SSH connections

Security:
- Command injection prevention using POSIX shell escaping
- Path traversal protection with restricted path validation
- Secure credential storage via system keychain
- Environment variable injection protection

Architecture:
- New services: SshService, RemoteGitService, RemotePtyService
- Type-safe IPC layer with comprehensive error handling
- React UI components with hooks for SSH management
- Database migration for ssh_connections table

Testing:
- TypeScript compilation: PASSED
- ESLint validation: PASSED
- Production build: PASSED
- Security review: 3 critical issues fixed

Breaking Changes: None (all changes are additive)

Files changed: 33 modified, ~40 new (~1,579 additions, ~75 deletions)
@vercel
Copy link

vercel bot commented Feb 9, 2026

@isEmmanuelOlowe is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link

greptile-apps bot commented Feb 9, 2026

Greptile Overview

Greptile Summary

This PR adds comprehensive SSH remote development support to Emdash, enabling users to run AI coding agents on remote servers. The implementation is well-architected with strong security foundations.

Key Changes:

  • SSH Services: SshService, SshCredentialService, SshHostKeyService provide connection management, secure credential storage via system keychain, and host key verification
  • Remote Operations: RemoteGitService, RemotePtyService, and RemoteFileSystem enable git operations, terminal sessions, and file system access over SSH/SFTP
  • Security Hardening: POSIX shell escaping (quoteShellArg), path traversal protection in isPathSafe(), credential separation (keychain vs database), and SSH_AUTH_SOCK detection
  • UI Components: Wizard-based SSH setup with ~/.ssh/config auto-detection, connection testing, and remote project browsing
  • Database Migration: Clean schema addition for ssh_connections table with proper indexes and foreign keys
  • Test Coverage: Comprehensive unit tests for SSH services, git operations, and credential management

Issue Found:

  • One command injection vulnerability in RemotePtyService.ts:73-75 where environment variable keys are not validated before shell injection (values are properly escaped, but keys bypass protection)

Overall Assessment:
The implementation demonstrates strong security awareness with proper shell escaping, credential isolation, and path validation throughout. The architecture is clean with good separation of concerns. Test coverage is thorough. The single security issue found is critical but localized and easily fixed.

Confidence Score: 4/5

  • Safe to merge after fixing the environment variable injection vulnerability in RemotePtyService
  • Strong security implementation with comprehensive shell escaping, secure credential storage, and thorough testing. However, one critical command injection vulnerability exists where environment variable keys are not validated before shell injection. All other security controls are properly implemented.
  • Pay close attention to src/main/services/RemotePtyService.ts - fix the env var key validation issue before merging

Important Files Changed

Filename Overview
src/main/services/ssh/SshService.ts Core SSH connection management with proper error handling, secure shell escaping, and connection pooling
src/main/services/ssh/SshCredentialService.ts Secure credential storage using system keychain via keytar, proper error handling throughout
src/main/utils/shellEnv.ts SSH agent detection with input validation, timeout protection, and comprehensive fallback logic
src/main/ipc/sshIpc.ts Comprehensive IPC handlers with path traversal protection and proper credential separation from database storage
src/main/services/RemoteGitService.ts Git operations over SSH with proper shell argument quoting and path normalization
src/main/services/RemotePtyService.ts Remote PTY sessions with POSIX shell escaping and proper cleanup on exit
src/main/services/fs/RemoteFileSystem.ts SFTP-based remote file operations with size limits, path traversal protection, and comprehensive error mapping
src/main/services/ssh/SshHostKeyService.ts Host key verification for MITM protection with known_hosts file management

Sequence Diagram

sequenceDiagram
    participant UI as React UI
    participant IPC as IPC Layer
    participant SSH as SshService
    participant Cred as SshCredentialService
    participant DB as Database
    participant Remote as Remote Server
    participant SFTP as SFTP/RemoteFS
    participant PTY as RemotePtyService
    participant Git as RemoteGitService

    Note over UI,Git: SSH Connection Setup
    UI->>IPC: sshTestConnection(config)
    IPC->>SSH: connect(config)
    SSH->>Cred: getPassword/getPassphrase
    Cred-->>SSH: credentials
    SSH->>Remote: SSH handshake
    Remote-->>SSH: connected
    SSH-->>IPC: connectionId
    IPC-->>UI: success

    UI->>IPC: sshSaveConnection(config)
    IPC->>Cred: storePassword/storePassphrase
    Cred->>Keychain: securely store credentials
    IPC->>DB: insert ssh_connections
    DB-->>IPC: saved
    IPC-->>UI: connection saved

    Note over UI,Git: Remote Project Operations
    UI->>IPC: openRemoteProject(connectionId, path)
    IPC->>SSH: connect(connectionId)
    SSH->>Cred: getPassword
    Cred-->>SSH: password
    SSH->>Remote: establish connection
    
    IPC->>SFTP: list(remotePath)
    SFTP->>SSH: getSftp(connectionId)
    SSH->>Remote: SFTP session
    Remote-->>SFTP: file listing
    SFTP-->>IPC: FileEntry[]
    IPC-->>UI: display files

    Note over UI,Git: Remote Git Worktree
    UI->>IPC: createRemoteWorktree(taskName)
    IPC->>Git: createWorktree(connectionId, projectPath, taskName)
    Git->>SSH: executeCommand("git worktree add...")
    SSH->>Remote: execute via SSH
    Remote-->>Git: worktree created
    Git-->>IPC: WorktreeInfo
    IPC-->>UI: worktree path

    Note over UI,Git: Remote Agent Execution
    UI->>IPC: startRemotePty(options)
    IPC->>PTY: startRemotePty(options)
    PTY->>SSH: getConnection(connectionId)
    SSH-->>PTY: connection
    PTY->>Remote: shell session with env & cwd
    Remote-->>PTY: PTY stream
    PTY-->>IPC: RemotePty instance
    IPC-->>UI: terminal ready

    UI->>IPC: ptyWrite(data)
    IPC->>PTY: write(ptyId, data)
    PTY->>Remote: stream.write(data)
    
    Remote-->>PTY: output data
    PTY-->>IPC: onData callback
    IPC-->>UI: terminal output

    Note over UI,Git: File Operations
    UI->>IPC: readRemoteFile(path)
    IPC->>SFTP: read(path, maxBytes)
    SFTP->>Remote: SFTP read
    Remote-->>SFTP: file contents
    SFTP-->>IPC: ReadResult
    IPC-->>UI: file content

    UI->>IPC: writeRemoteFile(path, content)
    IPC->>SFTP: write(path, content)
    SFTP->>Remote: SFTP write
    Remote-->>SFTP: success
    SFTP-->>IPC: WriteResult
    IPC-->>UI: file saved

    Note over UI,Git: Cleanup
    UI->>IPC: disconnect(connectionId)
    IPC->>PTY: kill(ptyId)
    PTY->>Remote: close stream
    IPC->>SSH: disconnect(connectionId)
    SSH->>Remote: end SSH connection
    Remote-->>SSH: closed
    SSH-->>IPC: disconnected
    IPC-->>UI: cleanup complete
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +73 to +75
const envVars = Object.entries(options.env || {})
.map(([k, v]) => `export ${k}=${quoteShellArg(v)}`)
.join(' && ');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variable keys are not validated before injection into shell command - a malicious key like FOO=bar; rm -rf / would bypass quoteShellArg protection

Suggested change
const envVars = Object.entries(options.env || {})
.map(([k, v]) => `export ${k}=${quoteShellArg(v)}`)
.join(' && ');
const envVars = Object.entries(options.env || {})
.map(([k, v]) => {
// Validate env var name (alphanumeric and underscore only)
if (!/^[A-Z_][A-Z0-9_]*$/i.test(k)) {
throw new Error(`Invalid environment variable name: ${k}`);
}
return `export ${k}=${quoteShellArg(v)}`;
})
.join(' && ');

@arnestrickmann
Copy link
Contributor

Hi @isEmmanuelOlowe!
Thank you for opening this PR. We‘ll take a look at it soon.
Have you already been using the ssh remote support yourself with Emdash?
Excited to learn more about your Development Flow!

@isEmmanuelOlowe
Copy link
Author

Hi @isEmmanuelOlowe! Thank you for opening this PR. We‘ll take a look at it soon. Have you already been using the ssh remote support yourself with Emdash? Excited to learn more about your Development Flow!

Yes I have! But only tested on MacOS connecting to linux servers. Further changes maybe required getting more things to support "Open In", MacOS terminal, VS Code, Cursor, should work just fine.

arnestrickmann added a commit that referenced this pull request Feb 10, 2026
…om PR #796

Merge conflicts resolved:
- Combined SSH remote project features (PR #796) with Skills features (main)
- Resolved 9 conflict files: ipc/index.ts, main.ts, preload.ts, App.tsx,
  LeftSidebar.tsx, TaskTerminalPanel.tsx, OpenInMenu.tsx, electron-api.d.ts,
  package-lock.json (removed, now pnpm)

Security fixes (CRITICAL):
- #1: Validate env var keys in RemotePtyService to prevent shell injection
- #2: Quote branch names in RemoteGitService.getDefaultBranch git rev-parse
- #3: Store algorithm alongside key in SshHostKeyService to prevent data loss

Security fixes (HIGH):
- #4: Consolidate 3 duplicate shell escape functions into shared utility
- #5: Add shell binary allowlist in RemotePtyService
- #6: Wait for SFTP close event in SshService.disconnect()
- #7: Improve isPathSafe() with segment-based traversal detection
- #8: Disconnect active SSH connection before deleting in sshIpc
- #9: Wire up reconnect event handler in SshConnectionMonitor

Type fixes from merge:
- Add missing 'process_exit' arg in ptyIpc.ts maybeMarkProviderFinish call
- Add missing Project type import in App.tsx
- Add isRemote/sshConnectionId to openIn type in electron-api.d.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants