This document explains the high-level architecture of SshManager, including how different components interact, key design decisions, and where to make common changes.
Target audience: Developers who need to understand the system design before making significant changes.
┌─────────────────────────────────────────────────────────────────┐
│ SshManager.App │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Views │ │ ViewModels │ │ Services │ │
│ │ (XAML) │──│ (MVVM) │──│ (App-level logic) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ SshManager │ │ SshManager │ │ SshManager │
│ .Terminal │ │ .Security │ │ .Data │
│ │ │ │ │ │
│ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Controls │ │ │ │ DPAPI │ │ │ │ EF Core │ │
│ │ Services │ │ │ │ KeyMgmt │ │ │ │ SQLite │ │
│ │ SSH.NET │ │ │ │ CredCache │ │ │ │ Repos │ │
│ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└────────────────────┼────────────────────┘
▼
┌───────────────┐
│ SshManager │
│ .Core │
│ │
│ ┌───────────┐ │
│ │ Models │ │
│ │ Enums │ │
│ └───────────┘ │
└───────────────┘
| Layer | Project | Responsibility |
|---|---|---|
| Presentation | SshManager.App | UI, ViewModels, user interaction |
| Terminal | SshManager.Terminal | SSH connections, terminal rendering |
| Security | SshManager.Security | Encryption, key management, PPK conversion, credentials |
| Data | SshManager.Data | Database access, repositories |
| Domain | SshManager.Core | Models, enums, shared types |
| Component | Technology | Why We Chose It |
|---|---|---|
| Framework | .NET 9 + WPF | Native Windows UI, mature ecosystem |
| UI Controls | WPF-UI 4.1.0 | Modern Fluent Design for WPF |
| MVVM | CommunityToolkit.Mvvm | Source generators, less boilerplate |
| Database | SQLite + EF Core | Embedded, zero-config, portable |
| SSH | SSH.NET | Mature, well-documented .NET SSH library |
| Terminal | xterm.js + WebView2 | Full VT100/ANSI support, GPU-accelerated |
| Logging | Serilog | Structured logging, multiple sinks |
sshmanager/
├── src/
│ ├── SshManager.Core/ # Domain layer (no dependencies)
│ │ └── Models/
│ │ ├── HostEntry.cs # SSH host configuration
│ │ ├── HostGroup.cs # Host grouping
│ │ ├── AuthType.cs # Authentication enum (SshAgent, PrivateKeyFile, Password, Kerberos, OnePassword)
│ │ ├── ConnectionType.cs # Connection type enum (Ssh, Serial)
│ │ ├── AppSettings.cs # Application settings model (~100+ properties)
│ │ ├── ConnectionHistory.cs # Connection history entries
│ │ ├── CommandSnippet.cs # Saved command snippets
│ │ ├── CommandHistoryEntry.cs # Per-host command history
│ │ ├── HostFingerprint.cs # SSH host key fingerprints
│ │ ├── ManagedSshKey.cs # Managed SSH key metadata
│ │ ├── ProxyJumpProfile.cs # Jump host configurations
│ │ ├── ProxyJumpHop.cs # Individual hops in proxy chain
│ │ ├── PortForwardingProfile.cs # Port forwarding configs
│ │ ├── SavedSession.cs # Session state for crash recovery
│ │ ├── TunnelProfile.cs # Visual tunnel builder profiles
│ │ ├── TunnelNode.cs # Tunnel graph nodes
│ │ ├── TunnelEdge.cs # Tunnel graph edges
│ │ ├── TerminalTheme.cs # Custom terminal color schemes
│ │ ├── HostProfile.cs # Reusable host templates
│ │ └── SerialPortSettings.cs # Serial port configuration
│ │
│ ├── SshManager.Data/ # Data access layer
│ │ ├── AppDbContext.cs # EF Core DbContext (17+ DbSets)
│ │ ├── DbPaths.cs # Database file location helper
│ │ ├── Configurations/ # EF Core entity configurations (19 files)
│ │ │ ├── HostEntryConfiguration.cs
│ │ │ ├── HostFingerprintConfiguration.cs
│ │ │ ├── SavedSessionConfiguration.cs
│ │ │ ├── TunnelProfileConfiguration.cs
│ │ │ └── ... # Other configurations
│ │ ├── Repositories/
│ │ │ ├── IHostRepository.cs # Host CRUD interface
│ │ │ ├── HostRepository.cs # Host CRUD implementation
│ │ │ ├── ISavedSessionRepository.cs # Crash recovery sessions
│ │ │ ├── ITunnelProfileRepository.cs # Tunnel profiles
│ │ │ ├── ICommandHistoryRepository.cs # Command history
│ │ │ └── ... # 16+ repositories
│ │ └── Services/
│ │ ├── ConnectionHistoryCleanupService.cs
│ │ └── HostCacheService.cs
│ │
│ ├── SshManager.Security/ # Security layer
│ │ ├── ISecretProtector.cs # Encryption interface
│ │ ├── DpapiSecretProtector.cs # DPAPI implementation
│ │ ├── ICredentialCache.cs # Credential cache interface
│ │ ├── SecureCredentialCache.cs # Secure in-memory cache
│ │ ├── ISshKeyManager.cs # SSH key management interface
│ │ ├── SshKeyManagerService.cs # SSH key operations
│ │ ├── IKeyEncryptionService.cs # Key passphrase management interface
│ │ ├── KeyEncryptionService.cs # Encrypt/decrypt/change key passphrases
│ │ ├── IPpkConverter.cs # PPK conversion interface
│ │ ├── PpkConverter.cs # Bidirectional PPK ↔ OpenSSH conversion
│ │ ├── OnePassword/ # 1Password CLI integration (IOnePasswordService, models)
│ │
│ ├── SshManager.Terminal/ # Terminal layer
│ │ ├── Controls/
│ │ │ ├── SshTerminalControl.xaml # Main terminal control
│ │ │ ├── WebTerminalControl.xaml # WebView2 xterm.js wrapper
│ │ │ ├── TerminalFindOverlay.xaml # Search overlay
│ │ │ └── TerminalStatusBar.xaml # Status bar control
│ │ │
│ │ ├── Services/
│ │ │ ├── Connection/ # SSH connection abstractions
│ │ │ ├── Display/ # Terminal theme and display
│ │ │ ├── Lifecycle/ # Session lifecycle hooks
│ │ │ ├── Processing/ # Terminal output processing
│ │ │ ├── Recording/ # ASCIINEMA v2 session recording
│ │ │ ├── Playback/ # Session playback with speed control
│ │ │ ├── Search/ # Terminal text search
│ │ │ ├── Stats/ # Terminal and server statistics
│ │ │ ├── ISshConnectionService.cs
│ │ │ ├── SshConnectionService.cs
│ │ │ ├── ISerialConnectionService.cs
│ │ │ ├── SerialConnectionService.cs
│ │ │ ├── SshTerminalBridge.cs
│ │ │ ├── SerialTerminalBridge.cs
│ │ │ ├── WebTerminalBridge.cs
│ │ │ ├── IKerberosAuthService.cs # Kerberos/GSSAPI auth
│ │ │ ├── IConnectionPool.cs # Connection reuse
│ │ │ ├── IX11ForwardingService.cs # X11 display forwarding
│ │ │ ├── ITunnelBuilderService.cs # Visual tunnel execution
│ │ │ ├── IAutocompletionService.cs # Command completion
│ │ │ ├── IBroadcastInputService.cs # Multi-terminal input
│ │ │ └── ... # 25+ services
│ │ │
│ │ └── Resources/
│ │ └── Terminal/
│ │ └── terminal.html # Embedded xterm.js terminal
│ │
│ └── SshManager.App/ # Application layer
│ ├── App.xaml # Application resources
│ ├── App.xaml.cs # Application entry, hosting
│ │
│ ├── Infrastructure/ # Modular DI service extensions
│ │ ├── AppServiceExtensions.cs
│ │ ├── DataServiceExtensions.cs
│ │ ├── SecurityServiceExtensions.cs
│ │ ├── TerminalServiceExtensions.cs
│ │ ├── HostedServiceExtensions.cs
│ │ ├── DbMigrator.cs
│ │ └── Bootstrapper.cs
│ │
│ ├── Services/
│ │ ├── Hosting/ # Background and startup services
│ │ │ ├── DatabaseInitializationHostedService.cs
│ │ │ ├── ThemeInitializationHostedService.cs
│ │ │ ├── SystemTrayHostedService.cs
│ │ │ ├── CredentialCacheHostedService.cs
│ │ │ └── StartupTasksHostedService.cs
│ │ ├── IExportImportService.cs
│ │ ├── ICloudSyncService.cs
│ │ ├── IBackupService.cs
│ │ ├── ISystemTrayService.cs
│ │ └── ... # Other app services
│ │
│ ├── Views/
│ │ ├── Windows/
│ │ │ ├── StartupWindow.xaml # Splash screen during init
│ │ │ ├── MainWindow.xaml # Main application window
│ │ │ ├── SftpBrowserWindow.xaml
│ │ │ └── TextEditorWindow.xaml
│ │ │
│ │ ├── Dialogs/ # 35+ dialogs
│ │ │ ├── HostEditDialog.xaml
│ │ │ ├── TunnelBuilderDialog.xaml
│ │ │ ├── PpkImportWizardDialog.xaml
│ │ │ └── ...
│ │ │
│ │ └── Controls/
│ │ ├── TerminalPane.xaml
│ │ ├── TerminalPaneContainer.xaml
│ │ ├── TunnelCanvas.xaml
│ │ └── ...
│ │
│ ├── ViewModels/ # 60+ view models
│ │
│ ├── Styles/
│ │ └── TerminalTheme.xaml # Terminal color resource dictionary
│ │
│ └── Converters/ # 34+ XAML value converters
│
├── tests/
│ ├── SshManager.Terminal.Tests/
│ └── SshManager.Security.Tests/
│
└── docs/ # Documentation
Purpose: Contains domain models and shared types. Has no external dependencies.
What goes here:
- Entity models (HostEntry, HostGroup, etc.)
- Enumerations (AuthType, etc.)
- Shared constants and interfaces
What doesn't go here:
- Business logic (put in appropriate layer)
- UI-related code (put in App)
- Database access code (put in Data)
When to add a file: When you need a new domain model or shared type.
Purpose: Data access layer using Entity Framework Core with SQLite.
What goes here:
- DbContext and entity configurations
- Repository implementations
- Database path helpers
What doesn't go here:
- Business logic beyond CRUD operations
- UI code
- SSH or terminal code
When to add a file: When you need a new repository or data access pattern.
Purpose: Security-related code including encryption, key management, and key format conversion.
What goes here:
- Encryption/decryption implementations (DPAPI)
- Credential caching
- SSH key management and generation
- Key encryption/passphrase management
- PPK ↔ OpenSSH key format conversion
- 1Password CLI integration (OnePassword/ folder)
- Security-related interfaces
What doesn't go here:
- SSH connection logic (put in Terminal)
- UI code
- Database access
When to add a file: When you need new security or key management functionality.
Purpose: SSH connections, serial connections, terminal rendering, and SSH agent interaction.
What goes here:
- SSH connection services (using SSH.NET)
- Serial connection services (System.IO.Ports + RJCP)
- Terminal WPF controls
- Terminal bridges (SSH <-> UI, Serial <-> UI)
- SSH agent services (diagnostics, key management)
- SFTP and port forwarding services
- Session recording and playback
- Auto-reconnection and connection pooling
- Kerberos/GSSAPI authentication
- X11 forwarding
- Terminal autocompletion
- Tunnel builder logic
Service Organization (nested folders):
Connection/- SSH connection abstractions and proxy chain buildingDisplay/- Terminal theme and display managementLifecycle/- Session lifecycle hooks and managementProcessing/- Terminal output processingRecording/- ASCIINEMA v2 session recordingPlayback/- Session playback with speed controlSearch/- Terminal text search servicesStats/- Terminal and server statistics collection
What doesn't go here:
- UI dialogs and windows (put in App)
- Data persistence (put in Data)
- Non-terminal UI controls
- Key format conversion (put in Security)
When to add a file: When you need new SSH, serial, terminal, or agent functionality.
Purpose: WPF application with UI and orchestration.
What goes here:
- Views (Windows, Dialogs, Controls)
- ViewModels
- Application services (import/export, backup, etc.)
- DI configuration in
Infrastructure/folder - Hosted services in
Services/Hosting/folder - Application lifecycle management
- XAML styles and resources in
Styles/folder
Key Folders:
Infrastructure/- Modular DI extension methods (AddDataServices, AddSecurityServices, etc.)Services/Hosting/- IHostedService implementations for startup and background tasksStyles/- XAML resource dictionaries (TerminalTheme.xaml)
What doesn't go here:
- Low-level SSH code (put in Terminal)
- Database access (put in Data)
- Encryption code (put in Security)
When to add a file: When you need new UI or application-level services.
User Double-clicks Host
│
▼
MainWindowViewModel.ConnectToHostAsync()
│
▼
TerminalSessionManager.CreateSessionAsync()
│
▼
SshConnectionService.ConnectAsync()
│
├── Password Auth: ISecretProtector.Unprotect()
├── PrivateKey Auth: Load key file
├── SshAgent Auth: Use Pageant/OpenSSH agent
├── Kerberos Auth: IKerberosAuthService.CreateAuth()
└── OnePassword Auth: IOnePasswordService.ReadAsync() → resolve op:// ref
│
▼
SSH.NET SshClient.Connect()
│
├── Host key verification callback
└── Keyboard-interactive callback (2FA)
│
▼
SshClient.CreateShellStream()
│
▼
SshTerminalBridge ←→ WebTerminalBridge ←→ xterm.js
│
└── (Optional) SessionRecorder captures output
Step-by-step:
-
User triggers connection in MainWindow
- Double-click, right-click Connect, or press Enter
-
ViewModel initiates connection in MainWindowViewModel
- Calls
TerminalSessionManager.CreateSessionAsync(hostEntry) - Creates new tab in terminal pane
- Calls
-
SSH connection established in SshConnectionService
- Decrypts password if needed via
ISecretProtector - Creates SSH.NET
SshClientwith appropriate authentication - Opens
ShellStreamfor terminal I/O
- Decrypts password if needed via
-
Terminal bridges created
SshTerminalBridge: Reads from ShellStream, writes to WebTerminalBridgeWebTerminalBridge: Sends data to xterm.js via WebView2 messaging
-
Terminal renders output
- xterm.js in WebView2 processes ANSI escape sequences
- User input sent back through the bridge chain
┌─────────────────┐
│ SSH Server │
└────────┬────────┘
│ TCP/SSH Protocol
▼
┌─────────────────┐
│ SSH.NET │
│ ShellStream │
└────────┬────────┘
│ byte[] data
▼
┌─────────────────┐ Reads data, buffers if terminal not ready
│ SshTerminalBridge│
└────────┬────────┘
│ string data
▼
┌─────────────────┐ PostWebMessageAsJson() to WebView2
│ WebTerminalBridge│
└────────┬────────┘
│ JSON: { type: "write", data: "..." }
▼
┌─────────────────┐
│ WebView2 │
│ (xterm.js) │──── Renders terminal, handles ANSI escapes
└────────┬────────┘
│ JSON: { type: "input", data: "..." }
▼
┌─────────────────┐ WebMessageReceived event
│ WebTerminalBridge│
└────────┬────────┘
│ string data
▼
┌─────────────────┐
│ SshTerminalBridge│──── Writes to ShellStream
└─────────────────┘
Services are registered using modular extension methods in SshManager.App/Infrastructure/:
// In App.xaml.cs or Bootstrapper.cs
services
.AddDataServices() // EF Core, repositories, data services
.AddSecurityServices() // DPAPI, credentials, SSH key management
.AddTerminalServices() // SSH, serial, terminal controls
.AddAppServices() // WPF-UI, ViewModels, Windows
.AddHostedServices(); // Startup and background servicesExtension Modules:
| Extension | File | Responsibilities |
|---|---|---|
AddDataServices() |
DataServiceExtensions.cs |
DbContextFactory, 16+ repositories, data services |
AddSecurityServices() |
SecurityServiceExtensions.cs |
DPAPI, credential cache, SSH key management, PPK |
AddTerminalServices() |
TerminalServiceExtensions.cs |
SSH/serial connections, sessions, recording |
AddAppServices() |
AppServiceExtensions.cs |
WPF-UI, ViewModels, cloud sync, system tray |
AddHostedServices() |
HostedServiceExtensions.cs |
Startup tasks, background services |
Hosted Services (startup order matters):
DatabaseInitializationHostedService- Creates DB, applies migrations, seeds dataThemeInitializationHostedService- Loads app and terminal themesSystemTrayHostedService- Initializes system tray iconCredentialCacheHostedService- Sets up credential cache with session lock monitoringStartupTasksHostedService- Connection history cleanup, other startup tasks
Background Services:
AutoBackupHostedService- Scheduled database backupsCloudSyncHostedService- OneDrive sync at intervalsHostStatusHostedService- Host availability monitoring
Startup Flow:
App.OnStartup()
│
├─→ Show StartupWindow (splash screen)
│
├─→ Build Generic Host
│ └─→ ConfigureServices() with extension methods
│
├─→ Start Hosted Services (in order)
│ ├─→ DatabaseInitializationHostedService
│ ├─→ ThemeInitializationHostedService
│ ├─→ SystemTrayHostedService
│ ├─→ CredentialCacheHostedService
│ └─→ StartupTasksHostedService
│
├─→ Close StartupWindow
│
└─→ Show MainWindow
What we decided: Use IDbContextFactory<AppDbContext> instead of direct DbContext injection.
Context:
- Repositories need to be singletons for performance
- DbContext is not thread-safe
- Multiple async operations may run concurrently
Why we decided this:
- Allows singleton repositories with properly scoped DbContexts
- Each operation gets its own DbContext via
CreateDbContextAsync() - DbContext is disposed after each operation via
await using
Trade-offs:
- Pro: Thread-safe data access with singleton repositories
- Pro: No DbContext lifetime management issues
- Con: Slightly more verbose code
- Con: No change tracking across operations
Code pattern:
public async Task<HostEntry?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
await using var db = await _dbFactory.CreateDbContextAsync(ct);
return await db.Hosts.FindAsync([id], ct);
}What we decided: Use WebView2 with xterm.js instead of a native WPF terminal control.
Context:
- Need full VT100/ANSI escape sequence support
- Need to render complex TUI applications (vim, htop, tmux)
- Native WPF terminal controls have limited escape sequence support
Why we decided this:
- xterm.js is battle-tested (used by VS Code, Theia, etc.)
- GPU-accelerated rendering via WebView2
- Full escape sequence support including 24-bit color
- Active community and maintenance
Trade-offs:
- Pro: Excellent terminal emulation quality
- Pro: Easy to customize themes and behavior
- Con: Requires WebView2 runtime (usually pre-installed)
- Con: JavaScript/C# bridge adds complexity
What we decided: Use Windows DPAPI for encrypting stored passwords.
Context:
- Need to securely store SSH passwords
- Passwords should be protected at rest
- Solution should be Windows-specific (acceptable for WPF app)
Why we decided this:
- DPAPI is built into Windows, no external dependencies
- Encryption is tied to the Windows user account
- Key management handled by Windows
Trade-offs:
- Pro: Strong encryption without key management complexity
- Pro: Passwords can't be decrypted by other Windows users
- Con: Passwords can't be transferred to other machines
- Con: Windows-only solution
What we decided: Use CommunityToolkit.Mvvm with source generators.
Context:
- WPF applications benefit from MVVM pattern
- Need to minimize boilerplate code
- Want compile-time safety
Why we decided this:
- Source generators create INotifyPropertyChanged implementation
[ObservableProperty]attribute reduces property boilerplate[RelayCommand]attribute simplifies command implementation- Full integration with Visual Studio and analyzers
Code pattern:
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private HostEntry? _selectedHost;
[RelayCommand]
private async Task ConnectAsync(HostEntry host)
{
// Command implementation
}
}SshManager.App
├─→ SshManager.Terminal
│ └─→ SshManager.Core
├─→ SshManager.Security
│ └─→ SshManager.Core
├─→ SshManager.Data
│ └─→ SshManager.Core
└─→ SshManager.Core
Dependencies flow downward only.
Core has no dependencies on other projects.
-
Core has no project dependencies
- Only models and shared types
- Can reference .NET base libraries only
-
Data, Security, Terminal depend only on Core
- These layers are independent of each other
- Can be tested in isolation
-
App depends on all projects
- Orchestrates all layers
- Contains DI configuration
-
No circular dependencies
- Dependencies always flow downward
| Package | Version | Used In | Purpose |
|---|---|---|---|
| Microsoft.EntityFrameworkCore.Sqlite | 9.0.0 | Data | Database access |
| SSH.NET | 2024.2.0 | Terminal | SSH connections |
| System.IO.Ports | 9.0.0 | Terminal | Serial port support (primary driver) |
| RJCP.SerialPortStream | Latest | Terminal | Serial port support (fallback driver) |
| Microsoft.Web.WebView2 | 1.0.2739.15 | Terminal | Terminal rendering |
| WPF-UI | 4.1.0 | App | UI controls |
| CommunityToolkit.Mvvm | 8.4.0 | App | MVVM framework |
| Serilog.Extensions.Hosting | 8.0.0 | App | Structured logging |
| Serilog.Sinks.File | Various | App | Log file output |
| H.NotifyIcon.Wpf | 2.1.3 | App | System tray icon |
| AvalonEdit | 6.3.0 | App | Text editor for remote files |
| FuzzySharp | 2.0.2 | App | Fuzzy search for quick connect |
| Konscious.Security.Cryptography.Argon2 | 1.3.1 | Security | PPK v3 key derivation |
| 1Password CLI | External | Security | Credential fetching from 1Password vaults |
- Add property to model in
SshManager.Core/Models/HostEntry.cs - Add database column migration in
App.xaml.cs:ApplySchemaMigrationsAsync() - Update UI in
SshManager.App/Views/Dialogs/HostEditDialog.xaml - Update ViewModel in
SshManager.App/ViewModels/HostEditViewModel.cs
- Add enum value to
SshManager.Core/Models/AuthType.cs - Handle in connection service
SshManager.Terminal/Services/SshConnectionService.cs - Update host edit UI to configure new auth type
- Add security handling if credentials need encryption
Note: OnePassword was the most recent auth type added. Because SshManager.Terminal does not depend on SshManager.Security, 1Password credentials are resolved in the App layer (SessionViewModel) before being passed to the Terminal layer via TerminalConnectionInfo.
- JavaScript side: Update
terminal.htmlto add new command handler - C# side: Update
WebTerminalBridge.csto send new commands - UI side: Add controls or menu items to trigger the feature
- Create interface in
SshManager.Data/Repositories/INewRepository.cs - Create implementation in
SshManager.Data/Repositories/NewRepository.cs - Add DbSet to
AppDbContext.csif new entity - Add entity configuration in
SshManager.Data/Configurations/ - Register in DI in
DataServiceExtensions.cs
- Create service class in
SshManager.App/Services/Hosting/- Implement
IHostedServiceor inherit fromBackgroundService
- Implement
- Register in DI in
HostedServiceExtensions.cs- Use
AddHostedService<T>()for startup services - Consider startup order if dependencies exist
- Use
- For singleton access, register as singleton first, then resolve:
services.AddSingleton<MyService>(); services.AddHostedService(sp => sp.GetRequiredService<MyService>());
- For new key formats: Update
IPpkConverterandPpkConverterin Security - For batch operations: Use
ConvertBatchToOpenSshAsyncpattern - For agent integration: Use
IAgentKeyService.AddKeyToAgentAsync() - For UI wizard: Follow
PpkImportWizardViewModelmulti-step pattern
- Configure retry policy in
ConnectionRetryPolicy.cs - Implement handler in
IAutoReconnectManager - Hook into session lifecycle via
ITerminalSessionLifecycle - Add per-host settings in
HostEntrymodel
- Add node type to
TunnelNodeTypeenum in Core - Update
TunnelBuilderServiceto handle new node type - Add UI representation in
TunnelCanvas.xaml.cs - Update
TunnelNodeViewModelfor new properties
- Implement completion provider in
IAutocompletionService - Add completion item type to
CompletionItemTypeenum - Update
TerminalAutocompletionHandlerto use new source - Configure in
AppSettingsfor user preferences
User enters password
│
▼
ISecretProtector.Protect(password)
│
▼
DpapiSecretProtector uses ProtectedData.Protect()
│
▼
Base64-encoded encrypted bytes stored in PasswordProtected column
│
▼
On connection, ISecretProtector.Unprotect() reverses the process
User connects with password/passphrase
│
▼
ICredentialCache.Store(key, credential, timeout)
│
▼
SecureCredentialCache stores in memory (encrypted)
│
▼
Automatic expiration after timeout
│
▼
Cleared on: Windows lock, app exit, manual clear
op:// reference stored in HostEntry
│
▼
SessionViewModel resolves at connection time
│
▼
op read "op://vault/item/field"
│
▼
Password or SSH key returned
│
▼
Passed to TerminalConnectionInfo
Credentials from 1Password are resolved in the App layer because SshManager.Terminal does not depend on SshManager.Security. For SSH keys, the key content is written to a secure temp file (with restrictive ACLs) and the path is passed via TerminalConnectionInfo.PrivateKeyPath. Temp key files are securely deleted (overwrite + delete) when the session closes.
- First connection: User prompted to verify fingerprint
- Fingerprint stored in
HostFingerprintstable - Subsequent connections: Fingerprint compared automatically
- Mismatch triggers warning dialog
Issue: DbContext disposed before async operation completes
- Symptoms:
ObjectDisposedExceptionin repository methods - Cause: Not using
await usingproperly - Solution: Always use
await using var db = await _dbFactory.CreateDbContextAsync(ct);
Issue: Terminal data loss on fast output
- Symptoms: Missing characters in terminal output
- Cause: Data arriving before xterm.js is ready
- Solution:
WebTerminalBridgebuffers data until "ready" message received
Issue: UI freeze during SSH operations
- Symptoms: Window becomes unresponsive during connection
- Cause: Blocking calls on UI thread
- Solution: All SSH operations must be async, use
Task.Run()for CPU-bound work
Issue: Memory leak with many terminal tabs
- Symptoms: Memory grows with each new tab
- Cause: WebView2 or bridges not properly disposed
- Solution: Ensure
SshTerminalBridge.Dispose()called when tab closes