-
Notifications
You must be signed in to change notification settings - Fork 108
feat: Add comprehensive SSH remote project support #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Add comprehensive SSH remote project support #796
Conversation
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)
|
@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 OverviewGreptile SummaryThis 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:
Issue Found:
Overall Assessment: Confidence Score: 4/5
|
| 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
There was a problem hiding this 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
| const envVars = Object.entries(options.env || {}) | ||
| .map(([k, v]) => `export ${k}=${quoteShellArg(v)}`) | ||
| .join(' && '); |
There was a problem hiding this comment.
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
| 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(' && '); |
|
Hi @isEmmanuelOlowe! |
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. |
…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>
Implements full SSH integration for remote development with security hardening:
Features:
Security:
Architecture:
Testing:
Breaking Changes: None (all changes are additive)
Files changed: 33 modified, ~40 new (~1,579 additions, ~75 deletions)