Skip to content

Add embedded SFTP browser and xterm.js SSH terminal#3258

Open
eran132 wants to merge 15 commits intomRemoteNG:v1.78.2-devfrom
eran132:feature/embedded-sftp-xterm-terminal
Open

Add embedded SFTP browser and xterm.js SSH terminal#3258
eran132 wants to merge 15 commits intomRemoteNG:v1.78.2-devfrom
eran132:feature/embedded-sftp-xterm-terminal

Conversation

@eran132
Copy link
Copy Markdown

@eran132 eran132 commented Apr 7, 2026

Summary

  • Replace PuTTY with xterm.js + WebView2 + SSH.NET for SSH terminal — eliminates external process embedding, window chrome artifacts (hidden min/max/close buttons), and provides native terminal rendering with full UTF-8/256-color support
  • Add MobaXterm-style embedded SFTP file browser that appears automatically alongside SSH terminal sessions in a split view
  • Fix Notifications panel — enable Info/Warning messages by default, fix first-connection message drop, add timestamps

SFTP Browser Features

  • Automatic split view: terminal (left) + SFTP browser (right) for SSH connections
  • File/folder icons with color coding (blue folders, green executables, red archives, purple images, teal symlinks)
  • Navigation: back/forward history, up, home, path bar
  • File operations: upload/download with progress bar, delete, rename, new file, new folder
  • Double-click text files to edit (downloads to temp, opens in default editor, auto re-uploads on save)
  • Right-click context menu, drag-and-drop upload, hidden files toggle
  • Connection status indicator, close/restore toggle

xterm.js Terminal

  • Uses WebView2 (already a project dependency) to render xterm.js
  • SSH connection via SSH.NET SshClient + ShellStream (already a project dependency)
  • Proper terminal resize handling
  • No external PuTTY process for SSH — eliminates window embedding hacks

Test plan

  • Connect to SSH host — verify xterm.js terminal renders correctly with UTF-8 characters
  • Verify SFTP browser appears automatically with file listing
  • Test file operations: upload, download, delete, rename, new file, new folder
  • Test double-click edit of text files
  • Test SFTP panel close/restore toggle
  • Verify RDP/VNC connections are unaffected (no SFTP panel)
  • Verify Notifications panel shows connection events with timestamps
  • Run existing unit tests — confirm no regressions

🤖 Generated with Claude Code

Replace PuTTY-based SSH terminal with native xterm.js + WebView2 + SSH.NET
implementation, eliminating external process embedding and window chrome
artifacts. Add MobaXterm-style embedded SFTP file browser for SSH connections.

SSH Terminal (xterm.js):
- Native terminal rendering via xterm.js in WebView2 control
- Full UTF-8 support, 256-color, mouse events
- No external PuTTY process needed for SSH connections
- Proper terminal resize handling via SSH.NET ShellStream
- Password authentication via SSH.NET SshClient

SFTP Browser (split view):
- Automatic SFTP panel alongside SSH terminal (MobaXterm-style)
- SplitContainer with resizable terminal/SFTP panels
- File/folder icons with color coding (executables, archives, images, configs, symlinks)
- Folders sorted first, then files alphabetically
- Back/forward/up/home navigation with history
- Upload (with progress bar), download, delete, rename, new file, new folder
- Double-click to edit text files (auto re-upload on save via FileSystemWatcher)
- Right-click context menu with full operations
- Hidden files toggle, drag-and-drop upload
- Connection status indicator (green/orange/red dot)
- Close/restore SFTP panel toggle button

Notifications improvements:
- Enable Info and Warning messages by default (were previously hidden)
- Fix first-connection messages being dropped (handle creation timing)
- Add timestamps to all notification messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 7, 2026 07:59
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add embedded SFTP browser and xterm.js SSH terminal, replacing PuTTY

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• **Replace PuTTY with xterm.js + WebView2 SSH terminal** — eliminates external process embedding
  and window chrome artifacts, provides native terminal rendering with full UTF-8/256-color support
• **Add embedded SFTP file browser** with automatic split view (terminal left, SFTP browser right)
  for SSH connections
• **SFTP browser features**: file/folder navigation with icons and color coding, upload/download
  with progress bar, delete/rename/create operations, double-click text file editing, drag-and-drop
  upload, context menu, hidden files toggle, connection status indicator
• **Fix Notifications panel** — enable Info/Warning messages by default, fix first-connection
  message drop, add timestamps to messages
• **Update SSH protocol implementations** — switch SSH1 and SSH2 from PuTTY-based to xterm.js-based
  terminal
• **Integrate SFTP browser** — auto-enable for SSH connections, add toggle to collapse/expand SFTP
  panel
• **Update PuTTY embedding** — fix window parent handle to work correctly with split view layout
• **Version bump** to 1.78.2.3468
Diagram
flowchart LR
  SSH["SSH Connection<br/>SSH1/SSH2"]
  TERM["xterm.js Terminal<br/>WebView2 + SSH.NET"]
  SFTP["SFTP Browser<br/>File Operations"]
  SPLIT["Split View<br/>Terminal + Browser"]
  NOTIF["Notifications Panel<br/>with Timestamps"]
  
  SSH -- "replaces PuTTY" --> TERM
  TERM -- "enables" --> SPLIT
  SFTP -- "appears in" --> SPLIT
  TERM -- "logs events to" --> NOTIF
Loading

Grey Divider

File Changes

1. mRemoteNG/UI/Controls/SftpBrowserPanel.cs ✨ Enhancement +953/-0

New embedded SFTP browser panel with file operations

• New 953-line embedded SFTP file browser control with full file operations (upload, download,
 delete, rename, create)
• Features include navigation history (back/forward), path bar, file type color coding,
 drag-and-drop upload, and double-click text file editing
• Implements connection retry logic with status indicator (gray/orange/green/red dots) and progress
 bar for transfers
• Supports hidden file toggle, context menu, and automatic file re-upload when edited in external
 editor

mRemoteNG/UI/Controls/SftpBrowserPanel.cs


2. mRemoteNG/UI/Window/SFTPBrowserWindow.cs ✨ Enhancement +785/-0

New standalone dockable SFTP browser window

• New 785-line standalone SFTP browser window (dockable panel) with auto-connect to active SSH tabs
• Implements toolbar with home, refresh, upload, download, new folder, delete, and disconnect
 buttons
• Auto-connects when SSH tab becomes active; tracks connection state and updates UI accordingly
• Includes file list with details view, drag-and-drop upload, and context menu operations

mRemoteNG/UI/Window/SFTPBrowserWindow.cs


3. mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs ✨ Enhancement +477/-0

New xterm.js-based SSH terminal using WebView2

• New 477-line SSH terminal implementation using WebView2 and xterm.js instead of PuTTY
• Handles SSH connection via SSH.NET SshClient and ShellStream with proper terminal resize
 handling
• Manages WebView2 initialization, resource extraction, and bidirectional message passing for
 terminal I/O
• Implements shell stream reading loop and UTF-8 binary data encoding/decoding via Base64

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs


View more (21)
4. mRemoteNG/UI/Controls/ConnectionContextMenu.cs ✨ Enhancement +35/-0

Add Properties menu item to connection context menu

• Added _cMenTreeProperties menu item to context menu for accessing connection properties
• Wired up OnPropertiesClicked handler to show the config form when Properties is clicked
• Added import statement for mRemoteNG.UI.Forms namespace

mRemoteNG/UI/Controls/ConnectionContextMenu.cs


5. mRemoteNG/Connection/InterfaceControl.cs ✨ Enhancement +124/-3

Add SFTP browser split view integration to InterfaceControl

• Added ProtocolPanel property that returns split view Panel1 if SFTP is enabled, otherwise
 returns the control itself
• New EnableSftpBrowser() method creates vertical SplitContainer with terminal (left) and SFTP
 browser (right)
• Implements ToggleSftpPanel() to collapse/expand SFTP panel and show/hide toggle button
• Added fields for _splitView, _sftpPanel, and _btnShowSftp toggle button

mRemoteNG/Connection/InterfaceControl.cs


6. mRemoteNG/Tools/SftpFileService.cs ✨ Enhancement +207/-0

New SFTP file service wrapper with async operations

• New 207-line SFTP service wrapper around SSH.NET SftpClient with async file operations
• Provides methods for connect, disconnect, list directory, upload, download, delete, rename, and
 create directory
• Uses SemaphoreSlim for thread-safe concurrent access; tracks current path and home directory
• Includes SftpFileItem class to represent file metadata (name, size, permissions, owner, modified
 time)

mRemoteNG/Tools/SftpFileService.cs


7. mRemoteNG/Connection/Protocol/PuttyBase.cs 🐞 Bug fix +7/-11

Update PuTTY embedding to use ProtocolPanel

• Changed PuTTY window parent handle from InterfaceControl.Handle to
 InterfaceControl.ProtocolPanel.Handle (3 occurrences)
• Updated Resize() method to use ProtocolPanel instead of InterfaceControl for calculating
 client rectangle
• Enables PuTTY to embed correctly when SFTP split view is active

mRemoteNG/Connection/Protocol/PuttyBase.cs


8. mRemoteNG/Properties/AssemblyInfo.cs ⚙️ Configuration changes +4/-4

Bump version to 1.78.2.3468

• Incremented build number from 3465 to 3468
• Updated AssemblyVersion, AssemblyFileVersion, and AssemblyInformationalVersion accordingly

mRemoteNG/Properties/AssemblyInfo.cs


9. mRemoteNG/UI/Menu/msMain/ToolsMenu.cs Formatting +3/-3

Formatting cleanup in ToolsMenu

• Minor formatting changes (whitespace cleanup in comments)

mRemoteNG/UI/Menu/msMain/ToolsMenu.cs


10. mRemoteNG/Messages/MessageWriters/NotificationPanelMessageWriter.cs 🐞 Bug fix +11/-0

Fix notification panel message drop on startup

• Added _handleEnsured flag to ensure control handle is created before adding messages
• Prevents early messages from being dropped during application startup

mRemoteNG/Messages/MessageWriters/NotificationPanelMessageWriter.cs


11. mRemoteNG/Properties/OptionsNotificationsPage.Designer.cs ⚙️ Configuration changes +2/-2

Enable Info and Warning notifications by default

• Changed default value for NotificationPanelWriterWriteInfoMsgs from False to True
• Changed default value for NotificationPanelWriterWriteWarningMsgs from False to True
• Enables Info and Warning messages in notification panel by default

mRemoteNG/Properties/OptionsNotificationsPage.Designer.cs


12. mRemoteNG/Connection/ConnectionInitiator.cs ✨ Enhancement +4/-0

Enable SFTP browser for SSH connections

• Added code to enable embedded SFTP browser for SSH1 and SSH2 connections after protocol
 initialization
• Calls EnableSftpBrowser() on the interface control with original connection info

mRemoteNG/Connection/ConnectionInitiator.cs


13. mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH1.cs ✨ Enhancement +4/-9

Switch SSH1 protocol to xterm.js terminal

• Changed ProtocolSSH1 base class from PuttyBase to SSHTerminalBase
• Removed PuTTY-specific constructor and protocol/version settings
• Added comment noting SSH1 is deprecated and SSH.NET connects as SSH2

mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH1.cs


14. mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs ✨ Enhancement +3/-9

Switch SSH2 protocol to xterm.js terminal

• Changed ProtocolSSH2 base class from PuttyBase to SSHTerminalBase
• Removed PuTTY-specific constructor and protocol/version settings

mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs


15. mRemoteNG/Connection/Protocol/ProtocolBase.cs ✨ Enhancement +1/-2

Add protocol control to ProtocolPanel instead of InterfaceControl

• Changed control parent from _interfaceControl.Controls to
 _interfaceControl.ProtocolPanel.Controls
• Allows protocol controls to be added to split view Panel1 when SFTP browser is enabled

mRemoteNG/Connection/Protocol/ProtocolBase.cs


16. mRemoteNG/UI/Forms/frmMain.cs ✨ Enhancement +1/-1

Adjust default window layout for config form

• Changed default layout to dock ConfigForm below TreeForm instead of to the left
• Uses AppWindows.TreeForm.Pane with DockAlignment.Bottom and 0.5 ratio for better space
 utilization

mRemoteNG/UI/Forms/frmMain.cs


17. mRemoteNG/UI/WindowType.cs ✨ Enhancement +1/-0

Add SFTPBrowser window type

• Added new SFTPBrowser = 17 enum value to WindowType enumeration

mRemoteNG/UI/WindowType.cs


18. mRemoteNG/UI/NotificationMessageListViewItem.cs ✨ Enhancement +1/-1

Add timestamps to notification messages

• Added timestamp prefix [HH:mm:ss] to notification messages
• Displays current time when message was added to the notification panel

mRemoteNG/UI/NotificationMessageListViewItem.cs


19. mRemoteNG/Connection/Protocol/SSH/Resources/xterm.css ✨ Enhancement +218/-0

New xterm.js CSS stylesheet

• New 218-line CSS stylesheet for xterm.js terminal styling
• Defines styles for terminal container, cursor, selection, text decorations, and accessibility
 features
• Includes color scheme and layout for xterm rendering

mRemoteNG/Connection/Protocol/SSH/Resources/xterm.css


20. mRemoteNG/Connection/Protocol/SSH/Resources/xterm-terminal.html ✨ Enhancement +124/-0

New xterm.js terminal HTML page

• New 124-line HTML page hosting xterm.js terminal with WebView2 integration
• Initializes xterm terminal with dark theme, fit addon, and message handlers for SSH I/O
• Implements bidirectional communication with C# backend via window.chrome.webview API

mRemoteNG/Connection/Protocol/SSH/Resources/xterm-terminal.html


21. mRemoteNG/Connection/Protocol/SSH/Resources/addon-fit.min.js Dependencies +8/-0

New xterm.js fit addon minified library

• New minified xterm.js fit addon (8 lines) for auto-fitting terminal to container size
• Enables responsive terminal resizing based on parent element dimensions

mRemoteNG/Connection/Protocol/SSH/Resources/addon-fit.min.js


22. mRemoteNG/mRemoteNG.csproj ⚙️ Configuration changes +6/-0

Add xterm.js resources to project

• Added 4 embedded resources for xterm.js terminal: xterm-terminal.html, xterm.min.js,
 xterm.css, addon-fit.min.js
• Resources are embedded in the project assembly for runtime extraction

mRemoteNG/mRemoteNG.csproj


23. mRemoteNG/Properties/OptionsNotificationsPage.settings ⚙️ Configuration changes +2/-2

Enable Info and Warning notifications by default

• Changed NotificationPanelWriterWriteInfoMsgs default from False to True
• Changed NotificationPanelWriterWriteWarningMsgs default from False to True

mRemoteNG/Properties/OptionsNotificationsPage.settings


24. mRemoteNG/Connection/Protocol/SSH/Resources/xterm.min.js Additional files +8/-0

...

mRemoteNG/Connection/Protocol/SSH/Resources/xterm.min.js


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 7, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Trusts any SSH hostkey🐞
Description
SSHTerminalBase unconditionally trusts the server host key by setting e.CanTrust=true, enabling
silent man-in-the-middle interception of SSH sessions. This impacts every SSH connection created by
the new terminal.
Code

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[R166-167]

+                // Auto-accept host keys (TODO: host key verification dialog)
+                _sshClient.HostKeyReceived += (s, e) => e.CanTrust = true;
Evidence
The new SSH terminal explicitly disables host-key verification by trusting every presented host key.

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[160-169]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SSHTerminalBase currently accepts any SSH host key (`e.CanTrust = true`), which removes host identity verification and enables MITM attacks.
## Issue Context
mRemoteNG previously relied on PuTTY’s known-host behavior; the new SSH.NET-based client must implement an equivalent trust decision.
## Fix Focus Areas
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[160-169]
## What to change
- Replace unconditional trust with a real decision:
- Compute and display fingerprint.
- Compare against a persisted known-hosts store (per host:port + key type).
- If unknown/changed, prompt the user to accept/reject and persist acceptance.
- If running headless/no prompt is possible, fail closed (do not connect) unless a matching known-host entry exists.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Connected event fired twice🐞
Description
SSHTerminalBase.Connect() calls base.Connect() (which immediately fires ConnectedEvent) before any
SSH connection is established, and then ConnectSshAsync fires Event_Connected again after
connecting. This can mark the tab/session as connected prematurely and/or trigger downstream
“connected” handlers twice.
Code

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[R117-134]

+        public override bool Connect()
+        {
+            try
+            {
+                if (_initTask != null && !_initTask.IsCompleted)
+                {
+                    // SSH connection will be triggered when xterm.js sends "ready" message
+                    _initTask.ContinueWith(t =>
+                    {
+                        if (t.IsFaulted)
+                        {
+                            Runtime.MessageCollector.AddExceptionStackTrace("SSHTerminal: Init task failed", t.Exception);
+                        }
+                    }, TaskScheduler.Default);
+                }
+
+                base.Connect();
+                return true;
Evidence
ProtocolBase.Connect() directly invokes ConnectedEvent, and SSHTerminalBase invokes that path
immediately in Connect(). Later, once SSH is actually connected, SSHTerminalBase again calls
Event_Connected(), which also invokes ConnectedEvent.

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[117-135]
mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[187-193]
mRemoteNG/Connection/Protocol/ProtocolBase.cs[131-137]
mRemoteNG/Connection/Protocol/ProtocolBase.cs[323-326]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SSHTerminalBase currently fires the protocol Connected event twice and fires it before the SSH connection exists. This can cause incorrect UI state and double-execution of connected handlers.
## Issue Context
- `ProtocolBase.Connect()` invokes `ConnectedEvent(this)` immediately.
- SSHTerminalBase calls `base.Connect()` inside `Connect()`.
- SSHTerminalBase also calls `Event_Connected(this)` after SSH connects.
## Fix Focus Areas
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[117-135]
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[187-193]
## What to change
- Do not call `base.Connect()` in `SSHTerminalBase.Connect()`.
- Instead, call `Event_Connecting(this)` (or another appropriate “connecting” signal) when initiating.
- Call **exactly one** connected signal after SSH is established:
- Either call `base.Connect()` once after SSH connection success, OR keep `Event_Connected(this)` and ensure `base.Connect()` is never called.
- Ensure failure paths do not emit Connected.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Temp xterm resources injectable🐞
Description
xterm HTML/JS/CSS are extracted to a predictable %TEMP% folder and skipped if files already exist,
allowing pre-existing/tampered files in that directory to be loaded by WebView2. This enables local
content injection into the embedded terminal UI.
Code

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[R53-57]

+                // Extract xterm.js resources to a temp folder for virtual host mapping
+                _resourceFolder = Path.Combine(
+                    Path.GetTempPath(),
+                    "mRemoteNG_XtermResources");
+                ExtractResources();
Evidence
The resource directory is a fixed path under %TEMP% ("mRemoteNG_XtermResources"), and
ExtractResources does not overwrite existing files. WebView2 maps and navigates to those files via a
virtual host, so any modified content in that folder is executed/rendered.

mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[47-58]
mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[422-456]
mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[83-98]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
WebView2 loads xterm assets from a predictable temp folder and the extraction code skips overwriting existing files. This allows pre-population/tampering of JS/CSS/HTML that WebView2 will execute.
## Issue Context
- `_resourceFolder` is `%TEMP%\\mRemoteNG_XtermResources`.
- `ExtractResources()` does `if (File.Exists(targetPath)) continue;`.
- WebView2 maps that folder and navigates to `https://xterm.local/xterm-terminal.html`.
## Fix Focus Areas
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[47-58]
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[422-456]
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[83-98]
## What to change
- Prefer one of:
1) Avoid temp files entirely: load HTML via `NavigateToString` and serve JS/CSS via `WebResourceRequested` from embedded resources.
2) If files must exist on disk: extract into a **unique per-session** directory (GUID), and delete it on close.
- Always overwrite/atomically write the expected embedded content (write to temp name then replace) rather than skipping when files exist.
- Consider using a more restrictive resource access kind than `Allow` if possible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. SSH key auth regressed🐞
Description
SSH1/SSH2 protocols now derive from SSHTerminalBase which only uses PasswordAuthenticationMethod and
does not integrate the existing external-credential/private-key flow used by PuttyBase. This breaks
connections that previously relied on external credential providers and/or private keys.
Code

mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs[R5-7]

[SupportedOSPlatform("windows")]
-    public class ProtocolSSH2 : PuttyBase
+    public class ProtocolSSH2 : SSHTerminalBase
{
-        public ProtocolSSH2()
-        {
-            PuttyProtocol = Putty_Protocol.ssh;
-            PuttySSHVersion = Putty_SSHVersion.ssh2;
-        }
-
Evidence
ProtocolSSH1/2 no longer inherit PuttyBase, so PuttyBase.Connect()’s external credential-provider
logic (including retrieving a private key and passing it via -i) is no longer used.
SSHTerminalBase constructs SSH.NET ConnectionInfo with only PasswordAuthenticationMethod, with no
path for private key authentication or external credential provider integration.

mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs[1-12]
mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH1.cs[1-14]
mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[147-163]
mRemoteNG/Connection/Protocol/PuttyBase.cs[78-150]
mRemoteNG/Connection/Protocol/PuttyBase.cs[266-270]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SSH1/SSH2 now use SSH.NET via SSHTerminalBase, but authentication is password-only. Existing functionality in PuttyBase supports external credential providers and private keys; that support is lost.
## Issue Context
- ProtocolSSH1/2 inherit SSHTerminalBase.
- SSHTerminalBase uses `new PasswordAuthenticationMethod(...)` only.
- PuttyBase previously fetched credentials/private keys from external providers and passed a temp key file to PuTTY via `-i`.
## Fix Focus Areas
- mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs[1-12]
- mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH1.cs[1-14]
- mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs[147-163]
## What to change
- Add support for key-based authentication (and external credential providers) when building SSH.NET `ConnectionInfo`:
- If an external credential provider is configured, fetch username/password/private key similarly to PuttyBase.
- When a private key is present, use `PrivateKeyAuthenticationMethod` (with passphrase if applicable) instead of (or in addition to) password.
- Consider supporting keyboard-interactive where required.
- If full parity is not feasible immediately, implement a safe fallback path: keep PuTTY-based SSH for connections that require private keys/external credential providers until SSH.NET path supports them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. SFTP edit watcher leak🐞
Description
SftpBrowserPanel’s edit feature writes to a deterministic temp filename using the host and remote
filename, which can contain invalid Windows filename characters and collide across sessions. If
Process.Start returns null (or an editor has no process handle), the FileSystemWatcher is never
disposed and remains active indefinitely, leaking resources and retaining the panel instance via
event handlers.
Code

mRemoteNG/UI/Controls/SftpBrowserPanel.cs[R634-723]

+                // Download to temp file
+                var tempDir = Path.Combine(Path.GetTempPath(), "mRemoteNG_SFTP");
+                Directory.CreateDirectory(tempDir);
+                var tempFile = Path.Combine(tempDir, $"{_host}_{Path.GetFileName(item.Name)}");
+
+                _lblStatus.Text = $"Downloading {item.Name} for editing...";
+                await _service.DownloadFileAsync(item.FullPath, tempFile);
+
+                // Track modification time
+                var lastWrite = File.GetLastWriteTimeUtc(tempFile);
+
+                // Open in default editor
+                var proc = Process.Start(new ProcessStartInfo(tempFile) { UseShellExecute = true });
+
+                _lblStatus.Text = $"Editing {item.Name} — save in editor to upload";
+
+                // Watch for file changes using FileSystemWatcher
+                var remotePath = item.FullPath;
+                var watcher = new FileSystemWatcher(tempDir, Path.GetFileName(tempFile))
+                {
+                    NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
+                    EnableRaisingEvents = true
+                };
+
+                // Debounce: editors may fire multiple write events
+                DateTime lastUpload = DateTime.MinValue;
+                watcher.Changed += async (ws, we) =>
+                {
+                    // Debounce — skip if we just uploaded within 2 seconds
+                    if ((DateTime.Now - lastUpload).TotalSeconds < 2) return;
+                    lastUpload = DateTime.Now;
+
+                    // Small delay to let the editor finish writing
+                    await System.Threading.Tasks.Task.Delay(500);
+
+                    try
+                    {
+                        BeginInvoke(async () =>
+                        {
+                            try
+                            {
+                                _lblStatus.Text = $"Uploading {item.Name}...";
+                                await _service.UploadFileAsync(tempFile, remotePath);
+                                _lblStatus.Text = $"Saved {item.Name}";
+                                Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg,
+                                    $"SFTP Edit: {item.Name} saved to {_host}:{remotePath}", true);
+                                _suppressHistory = true;
+                                await NavigateTo(_service.CurrentPath);
+                            }
+                            catch (Exception ex)
+                            {
+                                _lblStatus.Text = $"Upload failed: {ex.Message}";
+                            }
+                        });
+                    }
+                    catch { }
+                };
+
+                // If we got a process handle, clean up when editor closes
+                if (proc != null)
+                {
+                    _ = System.Threading.Tasks.Task.Run(async () =>
+                    {
+                        await proc.WaitForExitAsync();
+                        watcher.EnableRaisingEvents = false;
+                        watcher.Dispose();
+                        // Final check for changes
+                        if (File.Exists(tempFile) && File.GetLastWriteTimeUtc(tempFile) > lastWrite)
+                        {
+                            BeginInvoke(async () =>
+                            {
+                                try
+                                {
+                                    _lblStatus.Text = $"Uploading {item.Name}...";
+                                    await _service.UploadFileAsync(tempFile, remotePath);
+                                    _lblStatus.Text = $"Saved {item.Name}";
+                                    _suppressHistory = true;
+                                    await NavigateTo(_service.CurrentPath);
+                                }
+                                catch { }
+                            });
+                        }
+                        await System.Threading.Tasks.Task.Delay(1000);
+                        try { if (File.Exists(tempFile)) File.Delete(tempFile); } catch { }
+                    });
+                }
+                // If no process handle (editor already running), watcher stays active
+                // It will auto-upload on each save. Temp file persists until app close.
+            }
+            catch (Exception ex)
Evidence
The temp file path is built from _host and item.Name without sanitizing invalid filename
characters. The watcher is disposed only inside the if (proc != null) branch, and the code
explicitly states the watcher stays active otherwise.

mRemoteNG/UI/Controls/SftpBrowserPanel.cs[634-723]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SFTP edit flow creates a temp filename from host+remote filename (can be invalid on Windows and collide) and may leak FileSystemWatcher when no process handle is available.
## Issue Context
- Temp file: `Path.Combine(tempDir, $"{_host}_{Path.GetFileName(item.Name)}")`.
- Watcher is only disposed when `proc != null`; otherwise it remains active indefinitely.
## Fix Focus Areas
- mRemoteNG/UI/Controls/SftpBrowserPanel.cs[634-723]
## What to change
- Generate a safe unique temp filename (e.g., GUID) and keep a mapping to the remote path.
- Sanitize any user/remote-derived components using `Path.GetInvalidFileNameChars()` replacement if you still want readable names.
- Ensure `FileSystemWatcher` is always disposed:
- Track watchers in a field collection and dispose them in `Dispose(bool disposing)`.
- Add a timeout/cleanup mechanism when `proc == null`.
- Unsubscribe event handlers before disposing to avoid retaining the panel instance.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs Outdated
Comment thread mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs
Comment thread mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs Outdated
Comment thread mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes SSH sessions in mRemoteNG by replacing the embedded PuTTY terminal with an in-app WebView2 + xterm.js terminal backed by SSH.NET, and adds an embedded SFTP browser panel alongside SSH terminals. It also updates the Notifications panel defaults/behavior and tweaks the default docking layout.

Changes:

  • Introduce SSHTerminalBase (WebView2 + xterm.js + SSH.NET ShellStream) and switch SSH1/SSH2 protocols to use it.
  • Add an embedded SFTP browser (service + panel + optional window) and wire it into SSH connection startup via InterfaceControl split view.
  • Improve Notifications behavior (default Info/Warning enabled, early-message handle creation, timestamps in list items) and adjust default layout docking for Config panel.

Reviewed changes

Copilot reviewed 20 out of 24 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
mRemoteNG/UI/WindowType.cs Adds a new window type enum entry for SFTP browser.
mRemoteNG/UI/Window/SFTPBrowserWindow.cs New dockable SFTP browser window implementation (auto-connect logic + file ops).
mRemoteNG/UI/Controls/SftpBrowserPanel.cs New embedded SFTP panel (split-view companion for SSH tabs) with file operations/editing.
mRemoteNG/Tools/SftpFileService.cs New SSH.NET SftpClient wrapper with async ops and basic metadata.
mRemoteNG/Connection/InterfaceControl.cs Adds split-container support and SFTP panel enable/toggle for SSH sessions.
mRemoteNG/Connection/ConnectionInitiator.cs Enables SFTP split view automatically for SSH connections.
mRemoteNG/Connection/Protocol/SSH/SSHTerminalBase.cs New xterm.js terminal host using WebView2 and SSH.NET ShellStream.
mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH1.cs Switches SSH1 protocol implementation to SSHTerminalBase.
mRemoteNG/Connection/Protocol/SSH/Connection.Protocol.SSH2.cs Switches SSH2 protocol implementation to SSHTerminalBase.
mRemoteNG/Connection/Protocol/ProtocolBase.cs Routes protocol controls into InterfaceControl.ProtocolPanel (supports split view).
mRemoteNG/Connection/Protocol/PuttyBase.cs Updates PuTTY embedding to use ProtocolPanel as the parent container.
mRemoteNG/Connection/Protocol/SSH/Resources/* Adds embedded xterm.js HTML/CSS/JS resources.
mRemoteNG/mRemoteNG.csproj Embeds xterm.js resource files into the main assembly.
mRemoteNG/Messages/MessageWriters/NotificationPanelMessageWriter.cs Attempts to ensure early messages aren’t dropped by forcing handle creation.
mRemoteNG/UI/NotificationMessageListViewItem.cs Adds a timestamp prefix to notification items.
mRemoteNG/Properties/OptionsNotificationsPage.settings Enables Info/Warning notifications by default.
mRemoteNG/Properties/OptionsNotificationsPage.Designer.cs Updates default setting values to match the settings file.
mRemoteNG/UI/Forms/frmMain.cs Adjusts default layout docking (Config under Tree).
mRemoteNG/UI/Controls/ConnectionContextMenu.cs Adds a “Properties” item to activate/show the Config window.
mRemoteNG/UI/Menu/msMain/ToolsMenu.cs Minor comment formatting changes.
mRemoteNG/Properties/AssemblyInfo.cs Bumps build/version metadata.
Files not reviewed (1)
  • mRemoteNG/Properties/OptionsNotificationsPage.Designer.cs: Language not supported


ImageIndex = Convert.ToInt32(message.Class);
Text = message.Text.Replace(Environment.NewLine, " ");
Text = $"[{DateTime.Now:HH:mm:ss}] {message.Text.Replace(Environment.NewLine, " ")}";
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

NotificationMessageListViewItem uses DateTime.Now for the timestamp, which can differ from the message's own timestamp and makes ordering/debugging inconsistent. Use message.Date (from IMessage) when formatting the timestamp instead of the current time.

Suggested change
Text = $"[{DateTime.Now:HH:mm:ss}] {message.Text.Replace(Environment.NewLine, " ")}";
Text = $"[{message.Date:HH:mm:ss}] {message.Text.Replace(Environment.NewLine, " ")}";

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +32
// Ensure the handle is created on first use so early messages aren't dropped
if (!_handleEnsured && !_messageWindow.lvErrorCollector.IsDisposed)
{
if (!_messageWindow.lvErrorCollector.IsHandleCreated)
{
try { _ = _messageWindow.lvErrorCollector.Handle; } catch { }
}
_handleEnsured = true;
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The handle-creation workaround runs before the InvokeRequired check and may execute on a background thread. Accessing lvErrorCollector.Handle from a non-UI thread can create the control handle on the wrong thread or throw cross-thread exceptions. Ensure handle creation happens on the UI thread (e.g., move this logic into the InvokeRequired branch or use BeginInvoke on the owning form/control).

Copilot uses AI. Check for mistakes.
Comment on lines +234 to +235
if (connectionInfoOriginal.Protocol == ProtocolType.SSH1 || connectionInfoOriginal.Protocol == ProtocolType.SSH2)
newProtocol.InterfaceControl.EnableSftpBrowser(connectionInfoOriginal);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

This enables embedded SFTP using connectionInfoOriginal. When a connection is opened via an SSH tunnel, newProtocol.InterfaceControl.Info is modified to connect via localhost/local tunnel port, but the SFTP panel will attempt to connect directly to the original host/port and can fail or bypass the tunnel. Use the effective connection details (the modified connectionInfo / InterfaceControl.Info) for SFTP, or explicitly route SFTP through the same tunnel settings.

Suggested change
if (connectionInfoOriginal.Protocol == ProtocolType.SSH1 || connectionInfoOriginal.Protocol == ProtocolType.SSH2)
newProtocol.InterfaceControl.EnableSftpBrowser(connectionInfoOriginal);
// Use the effective runtime connection info so SFTP follows any SSH tunnel host/port rewrites.
if (connectionInfo.Protocol == ProtocolType.SSH1 || connectionInfo.Protocol == ProtocolType.SSH2)
newProtocol.InterfaceControl.EnableSftpBrowser(connectionInfo);

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +168
// Auto-accept host keys (TODO: host key verification dialog)
_sshClient.HostKeyReceived += (s, e) => e.CanTrust = true;

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

SSHTerminalBase currently auto-trusts any SSH host key (e.CanTrust = true). This removes host key verification and makes man-in-the-middle attacks trivial. Implement proper host key validation (known_hosts / fingerprint prompt + persistence) and fail the connection when the key is unknown or changed unless the user explicitly trusts it.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +58
_userDataFolder = Path.Combine(
Path.GetTempPath(),
"mRemoteNG_WebView2_SSH",
Guid.NewGuid().ToString());

// Extract xterm.js resources to a temp folder for virtual host mapping
_resourceFolder = Path.Combine(
Path.GetTempPath(),
"mRemoteNG_XtermResources");
ExtractResources();

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

xterm resources are extracted to a predictable, shared temp folder (mRemoteNG_XtermResources) and existing files are reused without integrity checks. Any process running as the same user could tamper with those files and get script execution inside the app's WebView2. Use a per-session unique folder (e.g., include a GUID) or serve the embedded resources from memory, and consider verifying content/overwriting on each run.

Copilot uses AI. Check for mistakes.
Comment on lines +635 to +638
var tempDir = Path.Combine(Path.GetTempPath(), "mRemoteNG_SFTP");
Directory.CreateDirectory(tempDir);
var tempFile = Path.Combine(tempDir, $"{_host}_{Path.GetFileName(item.Name)}");

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The temp file name includes _host verbatim. Hostnames can contain characters invalid for file names (notably IPv6 addresses with ':'), which will throw when building the path. Also, using only host+basename can collide when editing two files with the same name from different directories. Sanitize the host component (or hash it) and include a unique suffix and/or remote directory in the temp file name.

Copilot uses AI. Check for mistakes.
Comment on lines +692 to +722
// If we got a process handle, clean up when editor closes
if (proc != null)
{
_ = System.Threading.Tasks.Task.Run(async () =>
{
await proc.WaitForExitAsync();
watcher.EnableRaisingEvents = false;
watcher.Dispose();
// Final check for changes
if (File.Exists(tempFile) && File.GetLastWriteTimeUtc(tempFile) > lastWrite)
{
BeginInvoke(async () =>
{
try
{
_lblStatus.Text = $"Uploading {item.Name}...";
await _service.UploadFileAsync(tempFile, remotePath);
_lblStatus.Text = $"Saved {item.Name}";
_suppressHistory = true;
await NavigateTo(_service.CurrentPath);
}
catch { }
});
}
await System.Threading.Tasks.Task.Delay(1000);
try { if (File.Exists(tempFile)) File.Delete(tempFile); } catch { }
});
}
// If no process handle (editor already running), watcher stays active
// It will auto-upload on each save. Temp file persists until app close.
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

When Process.Start returns null (or when the editor is already running), the FileSystemWatcher is intentionally left running, but it's not tracked anywhere for later disposal and it will continue to fire even after disconnect/dispose. This risks leaking handles and uploading changes against a disposed/disconnected _service. Track watchers and dispose them on disconnect/dispose, and guard the Changed handler against _service being null/disconnected.

Copilot uses AI. Check for mistakes.
Comment on lines +916 to +918
// Files without extension that are commonly text (dotfiles)
var name = Path.GetFileName(fileName);
return name.StartsWith(".") && string.IsNullOrEmpty(ext);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

IsTextFile only treats extension-based matches or dotfiles as editable. Common extensionless text files like "Makefile" and "Dockerfile" will return false (Path.GetExtension is empty and the name doesn't start with '.'). Consider special-casing well-known filenames (e.g., Makefile, Dockerfile, README) so double-click edit behaves as intended.

Suggested change
// Files without extension that are commonly text (dotfiles)
var name = Path.GetFileName(fileName);
return name.StartsWith(".") && string.IsNullOrEmpty(ext);
var name = Path.GetFileName(fileName);
if (!string.IsNullOrEmpty(ext)) return false;
// Files without extension that are commonly text (dotfiles)
if (name.StartsWith(".", StringComparison.Ordinal)) return true;
// Common well-known extensionless text files
string[] wellKnownTextFileNames =
{
"Makefile",
"Dockerfile",
"README",
"LICENSE",
"CHANGELOG",
"AUTHORS",
"CONTRIBUTING",
"COPYING",
"INSTALL",
"NOTICE"
};
return wellKnownTextFileNames.Contains(name, StringComparer.OrdinalIgnoreCase);

Copilot uses AI. Check for mistakes.
{
try
{
AppWindows.ConfigForm.Show(FrmMain.Default.pnlDock);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

Calling ConfigForm.Show(FrmMain.Default.pnlDock) may re-dock the configuration panel and fight the default layout (which docks ConfigForm under TreeForm). Other code paths (e.g., PanelBinder) use ConfigForm.Show() + Activate to bring the existing panel forward without changing docking. Consider using Show() (or showing in TreeForm.Pane) here to avoid disrupting the user's layout.

Suggested change
AppWindows.ConfigForm.Show(FrmMain.Default.pnlDock);
AppWindows.ConfigForm.Show();

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +158
_btnShowSftp = new Button
{
Text = "SFTP",
Width = 22,
Dock = DockStyle.Right,
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

_btnShowSftp (DockStyle.Right) is added to Panel1 before the protocol control (DockStyle.Fill) is added later by ProtocolBase.Initialize. With WinForms docking/z-order, adding the Fill control after the right-docked button can cause Dock.Fill to occupy all space and the button to be overlapped/not laid out when made Visible. Consider ensuring the Fill control is added first (or adjust child index / BringToFront after protocol control is added).

Copilot uses AI. Check for mistakes.
eran132 and others added 14 commits April 7, 2026 11:22
Security fixes:
- Use per-session GUID folder for xterm resources (prevents tampering)
- Replace Path.GetTempFileName with Path.GetRandomFileName (S5445)
- Sanitize hostname in temp file paths (IPv6 colon handling)
- Add host key fingerprint logging (preparation for full verification)
- Add keyboard-interactive auth as fallback alongside password auth

Bug fixes:
- Dispose CancellationTokenSource _readCts on close (S2930 blocker)
- Fix double Connected event — don't call base.Connect() prematurely
- Use message.Date for notification timestamps instead of DateTime.Now
- Fix handle creation threading in NotificationPanelMessageWriter
- Use effective connectionInfo for SFTP (follows SSH tunnel routing)
- Track and dispose FileSystemWatchers on disconnect
- Fix ConfigForm.Show docking disruption
- Fix SFTP toggle button z-order with BringToFront

Code quality:
- Rename SSHTerminalBase to SshTerminalBase (S101 naming convention)
- Add lang="en" to xterm HTML (accessibility)
- Add IsSymlink flag to SftpFileItem (proper symlink detection)
- Handle null password in SftpFileService
- Add well-known extensionless text files (Makefile, Dockerfile, etc.)
- Remove unused htmlPath variable, validate file exists before navigate
- Clean up xterm resource folder on session close
- Always overwrite resources in per-session folder
- Replace empty catch blocks with specific exception types
- Use LINQ FirstOrDefault in FindResourceName

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SSHTerminalBase.ConnectSshAsync: Extract BuildAuthMethods,
  StartShellSession, ExecuteOpeningCommand, InvokeOnUiThread helpers
- SSHTerminalBase.ReadShellStreamAsync: Extract PostDataToWebView,
  OnConnectionClosed helpers
- SftpBrowserPanel.OnEditFileClick: Extract DownloadToTempFile,
  CreateEditWatcher, UploadEditedFile, WaitForEditorClose helpers
- SFTPBrowserWindow.GetActiveSSHConnectionInfo: Extract
  GetSshInfoFromTab helper to deduplicate null-check pattern

All four methods previously exceeded SonarCloud S3776 cognitive
complexity threshold of 15 (were 20, 20, 26, 25 respectively).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract EnsureHandleCreated and InvokeOnCorrectThread helper methods
from AddToList to bring cognitive complexity below SonarCloud S3776
threshold of 15.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unreachable code: methods.Count == 0 check after always
  adding kbInteractive (S2583 - condition always false)
- Change UploadEditedFile from async void to async Task (S3168)
- Remove unused assemblyWriteTime variable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SHA-384 integrity attributes to CSS and JS tags in the terminal
HTML to detect tampering of extracted resources. Provides defense in
depth for air-gapped environments where temp folder files could be
modified between extraction and WebView2 loading.

Resolves SonarCloud security hotspots S5725.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add comments to empty catch blocks explaining why exceptions are
  ignored (S108/S2486 — 8 occurrences across 5 files)
- Remove redundant null checks where condition always true (S2589)
- Replace DateTime.Now with Stopwatch for debounce timing (S6561)
- Fix IDisposable pattern in SftpFileService (S3881)
- Remove unreachable dead code in BuildAuthMethods
- Remove unused assemblyWriteTime variable
- Use specific exception types in catch blocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSHTerminalBase:
- Remove _webView2Environment field (use local variable)
- Make OnWebView2InitCompleted and OnHostKeyReceived static
- Add comments to empty catch blocks

xterm-terminal.html:
- Replace const/let with var to avoid lexical declarations in switch
- Use globalThis instead of window
- Use codePointAt instead of charCodeAt
- Replace Object.assign with manual property copy
- Add comment to empty catch block

SftpBrowserPanel:
- Add RemotePathSeparator and DefaultFont constants
- Replace hardcoded '/' and "Segoe UI" with constants

SFTPBrowserWindow:
- Rename class to SftpBrowserWindow (S101 PascalCase)
- Add RemotePathSeparator and StatusNotConnected constants
- Make GetActiveSSHConnectionInfo static
- Use != instead of negated == (S1940)

ConnectionContextMenu:
- Make OnPropertiesClicked static

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert 20 UI control fields to local variables (S1450)
  in SftpBrowserPanel and SftpBrowserWindow
- Fix redundant disposal by restructuring TryConnect (S3966)
- Fix xterm.js: use _ for unused catch params (S2486),
  use for-of loop (S4138)
- Remove _webView2Environment field, make methods static

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace unused catch parameters with console.debug logging
to satisfy javascript:S2486 (handle or remove catch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move duplicated PromptInput method from SftpBrowserPanel and
SftpBrowserWindow into a shared InputDialog.Prompt utility class.
Eliminates the only code duplication flagged by SonarCloud.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unit tests (14 new tests):
- SftpFileItem: default values, property retention, directory/symlink
- SftpFileService: initial state, dispose, double dispose, invalid host
- SRI hash: SHA-384 format, determinism, script/link tag injection
- InputDialog: method existence verification

UX improvements:
- Permissions column now shows full 10-char format with file type
  prefix (d/l/-/b/c/p/s) matching ls -l output
- Owner column shows UID:GID format
- Column renamed from "Perms" to "Permissions", widened to 80px
- Keyboard shortcuts: F5=refresh, Del=delete, F2=rename, Enter=open,
  Backspace=up

Security:
- Runtime SRI hash injection: compute SHA-384 of extracted files and
  inject integrity attributes into HTML before WebView2 loads it
- Eliminates CRLF hash mismatch that broke static SRI hashes
- Per-session GUID folders + runtime SRI = defense in depth

Code quality:
- Make SftpFileService, SftpFileItem, InputDialog public for testability
- Add LinkTarget property to SftpFileItem for future symlink display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused searchTag and searchLink variables in InjectSriHashes
- Extract GetFileTypeChar from FormatPermissions to reduce cognitive
  complexity from 18 to below 15 (S3776)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace external <script src> and <link href> tags with runtime
resource inlining. SshTerminalBase.InlineResources() reads the
extracted CSS/JS files and embeds their content directly into the
HTML as <style> and <script> blocks before WebView2 loads it.

This eliminates all 3 SonarCloud S5725 security hotspots (missing
SRI on resource tags) since there are no external resource tags
in the final HTML. The approach is more secure than SRI — the
resources never exist as separate loadable URLs.

Updated tests to verify inline replacement logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace <script src> and <link href> tags with <!-- INLINE:file -->
comment markers in the HTML template. SshTerminalBase.InlineResources
replaces these markers with actual file content at runtime.

SonarCloud S5725 scans static source files and flagged the external
resource URLs even though they pointed to local virtual host mappings.
Comment markers are invisible to S5725 since they contain no src/href
attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 8, 2026

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