From 415a649a76ddeb2a75b5280c6cca279ee7aa8b8e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 12:55:04 +0000
Subject: [PATCH 01/10] Initial plan
From b7ed5a300d430f6fb857e917a13448cc55d6717a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 13:03:54 +0000
Subject: [PATCH 02/10] Fix command injection vulnerabilities in Process.Start
calls
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
---
mRemoteNG/App/Shutdown.cs | 4 ++
.../Protocol/AnyDesk/ProtocolAnyDesk.cs | 63 ++++++++++++++++---
mRemoteNG/UI/Forms/FrmAbout.cs | 37 +++++++++--
mRemoteNG/UI/Forms/FrmUnhandledException.cs | 7 ++-
.../Forms/OptionsPages/NotificationsPage.cs | 35 ++++++++++-
mRemoteNG/UI/Menu/msMain/HelpMenu.cs | 24 ++++---
mRemoteNG/UI/Window/UpdateWindow.cs | 7 ++-
7 files changed, 150 insertions(+), 27 deletions(-)
diff --git a/mRemoteNG/App/Shutdown.cs b/mRemoteNG/App/Shutdown.cs
index f577c8f46..c27dd238d 100644
--- a/mRemoteNG/App/Shutdown.cs
+++ b/mRemoteNG/App/Shutdown.cs
@@ -121,7 +121,11 @@ namespace mRemoteNG.App
private static void RunUpdateFile()
{
if (UpdatePending)
+ {
+ // Validate the update file path to prevent command injection
+ Tools.PathValidator.ValidateExecutablePathOrThrow(_updateFilePath, nameof(_updateFilePath));
Process.Start(new ProcessStartInfo(_updateFilePath) { UseShellExecute = true });
+ }
}
}
}
\ No newline at end of file
diff --git a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
index e6df6a9f5..3206d5b8f 100644
--- a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
+++ b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
@@ -211,6 +211,16 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
// Username field is optional and not used in the CLI (reserved for future use)
// Password field is piped via stdin when --with-password flag is used
string anydeskId = _connectionInfo.Hostname.Trim();
+
+ // Validate AnyDesk ID to prevent command injection
+ // AnyDesk IDs are numeric or alphanumeric with @ and - characters for aliases
+ if (!IsValidAnydeskId(anydeskId))
+ {
+ Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
+ "Invalid AnyDesk ID format. Only alphanumeric characters, @, -, and _ are allowed.", true);
+ return false;
+ }
+
string arguments = $"{anydeskId}";
// Add --with-password flag if password is provided
@@ -242,27 +252,45 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
return false;
}
}
+
+ private bool IsValidAnydeskId(string anydeskId)
+ {
+ if (string.IsNullOrWhiteSpace(anydeskId))
+ return false;
+
+ // AnyDesk IDs can be:
+ // - Pure numeric (e.g., 123456789)
+ // - Alphanumeric with @ for aliases (e.g., alias@ad)
+ // - May contain hyphens and underscores in aliases
+ // Reject any characters that could be used for command injection
+ foreach (char c in anydeskId)
+ {
+ if (!char.IsLetterOrDigit(c) && c != '@' && c != '-' && c != '_' && c != '.')
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
private bool StartAnydeskWithPassword(string anydeskPath, string arguments)
{
try
{
- // Use PowerShell to pipe the password to AnyDesk
- // This is the recommended way according to AnyDesk documentation
- string escapedPassword = _connectionInfo.Password.Replace("'", "''");
- string powershellCommand = $"echo '{escapedPassword}' | & '{anydeskPath}' {arguments}";
-
+ // Start AnyDesk and pipe the password directly to stdin
+ // This avoids command injection vulnerabilities from using PowerShell
_process = new Process
{
StartInfo = new ProcessStartInfo
{
- FileName = "powershell.exe",
- Arguments = $"-WindowStyle Hidden -Command \"{powershellCommand}\"",
+ FileName = anydeskPath,
+ Arguments = arguments,
UseShellExecute = false,
- CreateNoWindow = true,
+ CreateNoWindow = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
- RedirectStandardInput = false
+ RedirectStandardInput = true
},
EnableRaisingEvents = true
};
@@ -270,8 +298,23 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
_process.Exited += ProcessExited;
_process.Start();
+ // Write the password to stdin and close the stream
+ // AnyDesk expects the password on stdin when --with-password is used
+ try
+ {
+ if (_process.StandardInput != null)
+ {
+ _process.StandardInput.WriteLine(_connectionInfo.Password);
+ _process.StandardInput.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
+ $"Failed to send password to AnyDesk: {ex.Message}", true);
+ }
+
// Wait for the AnyDesk window to appear
- // Note: The window belongs to the AnyDesk process, not PowerShell
if (!WaitForAnydeskWindow())
{
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
diff --git a/mRemoteNG/UI/Forms/FrmAbout.cs b/mRemoteNG/UI/Forms/FrmAbout.cs
index 06e8be286..969c1879a 100644
--- a/mRemoteNG/UI/Forms/FrmAbout.cs
+++ b/mRemoteNG/UI/Forms/FrmAbout.cs
@@ -79,23 +79,50 @@ namespace mRemoteNG.UI.Forms
{
try
{
- Process.Start(url);
+ // Try to open URL with UseShellExecute
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = url,
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- url = url.Replace("&", "^&");
- Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
+ // Use ArgumentList for better security instead of string concatenation
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "cmd",
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ startInfo.ArgumentList.Add("/c");
+ startInfo.ArgumentList.Add("start");
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- Process.Start("xdg-open", url);
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "xdg-open",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- Process.Start("open", url);
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "open",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
}
else
{
diff --git a/mRemoteNG/UI/Forms/FrmUnhandledException.cs b/mRemoteNG/UI/Forms/FrmUnhandledException.cs
index c42e27cd2..8f4310600 100644
--- a/mRemoteNG/UI/Forms/FrmUnhandledException.cs
+++ b/mRemoteNG/UI/Forms/FrmUnhandledException.cs
@@ -91,7 +91,12 @@ namespace mRemoteNG.UI.Forms
private void buttonCreateBug_Click(object sender, EventArgs e)
{
- Process.Start(GeneralAppInfo.UrlBugs);
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = GeneralAppInfo.UrlBugs,
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
}
}
}
\ No newline at end of file
diff --git a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
index a2701cf74..5bda1518a 100644
--- a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
+++ b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
@@ -342,8 +342,17 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
+ // Validate path to prevent command injection
+ Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+
// Open the file using the default application associated with its file type based on the user's preference
- Process.Start(path);
+ // Use ProcessStartInfo with UseShellExecute for better control
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = path,
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
return true;
}
catch
@@ -362,9 +371,19 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
+ // Validate path to prevent command injection
+ Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+
// Open it in "Notepad" (Windows default editor).
// Usually available on all Windows systems
- Process.Start("notepad.exe", path);
+ // Use ProcessStartInfo with ArgumentList for better security
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "notepad.exe",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add(path);
+ Process.Start(startInfo);
return true;
}
catch
@@ -383,9 +402,19 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
+ // Validate path to prevent command injection
+ Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+
// when all fails open filelocation to logfile...
// Open Windows Explorer to the directory containing the file
- Process.Start("explorer.exe", $"/select,\"{path}\"");
+ // Use ArgumentList for better security
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "explorer.exe",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add("/select," + path);
+ Process.Start(startInfo);
return true;
}
catch
diff --git a/mRemoteNG/UI/Menu/msMain/HelpMenu.cs b/mRemoteNG/UI/Menu/msMain/HelpMenu.cs
index e948b53e4..89adeb9f8 100644
--- a/mRemoteNG/UI/Menu/msMain/HelpMenu.cs
+++ b/mRemoteNG/UI/Menu/msMain/HelpMenu.cs
@@ -188,19 +188,29 @@ namespace mRemoteNG.UI.Menu
}
}
- private void mMenInfoHelp_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDocumentation);
+ private void mMenInfoHelp_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDocumentation);
- private void mMenInfoForum_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlForum);
+ private void mMenInfoForum_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlForum);
- private void mMenInfoChat_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlChat);
+ private void mMenInfoChat_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlChat);
- private void mMenInfoCommunity_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlCommunity);
+ private void mMenInfoCommunity_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlCommunity);
- private void mMenInfoBug_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlBugs);
+ private void mMenInfoBug_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlBugs);
- private void mMenInfoWebsite_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlHome);
+ private void mMenInfoWebsite_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlHome);
- private void mMenInfoDonate_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDonate);
+ private void mMenInfoDonate_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDonate);
+
+ private static void OpenUrl(string url)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = url,
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
+ }
private void mMenInfoAbout_Click(object? sender, EventArgs e)
{
diff --git a/mRemoteNG/UI/Window/UpdateWindow.cs b/mRemoteNG/UI/Window/UpdateWindow.cs
index d590152d8..935940207 100644
--- a/mRemoteNG/UI/Window/UpdateWindow.cs
+++ b/mRemoteNG/UI/Window/UpdateWindow.cs
@@ -99,7 +99,12 @@ namespace mRemoteNG.UI.Window
return;
}
- Process.Start(linkUri.ToString());
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = linkUri.ToString(),
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
}
#endregion
From 843243c75e325ff0a8ae0b168170a648b3c78a1b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 13:06:16 +0000
Subject: [PATCH 03/10] Add comprehensive tests for AnyDesk ID validation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
---
.../Protocol/ProtocolAnydeskTests.cs | 204 ++++++++++++++++++
1 file changed, 204 insertions(+)
create mode 100644 mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
diff --git a/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs b/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
new file mode 100644
index 000000000..9582846d2
--- /dev/null
+++ b/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Reflection;
+using mRemoteNG.Connection;
+using mRemoteNG.Connection.Protocol.AnyDesk;
+using NUnit.Framework;
+
+namespace mRemoteNGTests.Connection.Protocol;
+
+public class ProtocolAnydeskTests
+{
+ private ProtocolAnyDesk _protocolAnydesk;
+ private ConnectionInfo _connectionInfo;
+
+ [SetUp]
+ public void Setup()
+ {
+ _connectionInfo = new ConnectionInfo();
+ _protocolAnydesk = new ProtocolAnyDesk(_connectionInfo);
+ }
+
+ [TearDown]
+ public void Teardown()
+ {
+ _protocolAnydesk?.Close();
+ _protocolAnydesk = null;
+ _connectionInfo = null;
+ }
+
+ #region IsValidAnydeskId Tests
+
+ [Test]
+ public void IsValidAnydeskId_NumericId_ReturnsTrue()
+ {
+ // Valid numeric AnyDesk ID
+ bool result = InvokeIsValidAnydeskId("123456789");
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_AlphanumericWithAt_ReturnsTrue()
+ {
+ // Valid alias format: alias@ad
+ bool result = InvokeIsValidAnydeskId("myalias@ad");
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithHyphen_ReturnsTrue()
+ {
+ // Valid ID with hyphen
+ bool result = InvokeIsValidAnydeskId("my-alias@ad");
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithUnderscore_ReturnsTrue()
+ {
+ // Valid ID with underscore
+ bool result = InvokeIsValidAnydeskId("my_alias@ad");
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithDot_ReturnsTrue()
+ {
+ // Valid ID with dot
+ bool result = InvokeIsValidAnydeskId("alias.name@ad");
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithSemicolon_ReturnsFalse()
+ {
+ // Command injection attempt with semicolon
+ bool result = InvokeIsValidAnydeskId("123456789; calc.exe");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithAmpersand_ReturnsFalse()
+ {
+ // Command injection attempt with ampersand
+ bool result = InvokeIsValidAnydeskId("123456789 & calc.exe");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithPipe_ReturnsFalse()
+ {
+ // Command injection attempt with pipe
+ bool result = InvokeIsValidAnydeskId("123456789 | calc.exe");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithRedirection_ReturnsFalse()
+ {
+ // Command injection attempt with redirection
+ bool result = InvokeIsValidAnydeskId("123456789 > output.txt");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithBacktick_ReturnsFalse()
+ {
+ // PowerShell escape character
+ bool result = InvokeIsValidAnydeskId("123456789`calc");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithDollarSign_ReturnsFalse()
+ {
+ // PowerShell variable indicator
+ bool result = InvokeIsValidAnydeskId("123456789$var");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithParentheses_ReturnsFalse()
+ {
+ // Command substitution
+ bool result = InvokeIsValidAnydeskId("123456789(calc)");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithNewline_ReturnsFalse()
+ {
+ // Newline injection
+ bool result = InvokeIsValidAnydeskId("123456789\ncalc");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithCarriageReturn_ReturnsFalse()
+ {
+ // Carriage return injection
+ bool result = InvokeIsValidAnydeskId("123456789\rcalc");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithQuotes_ReturnsFalse()
+ {
+ // Quote escape attempt
+ bool result = InvokeIsValidAnydeskId("123456789\"calc\"");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_WithSingleQuotes_ReturnsFalse()
+ {
+ // Single quote escape attempt
+ bool result = InvokeIsValidAnydeskId("123456789'calc'");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_EmptyString_ReturnsFalse()
+ {
+ // Empty string
+ bool result = InvokeIsValidAnydeskId("");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_Whitespace_ReturnsFalse()
+ {
+ // Only whitespace
+ bool result = InvokeIsValidAnydeskId(" ");
+ Assert.That(result, Is.False);
+ }
+
+ [Test]
+ public void IsValidAnydeskId_Null_ReturnsFalse()
+ {
+ // Null string
+ bool result = InvokeIsValidAnydeskId(null);
+ Assert.That(result, Is.False);
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Uses reflection to invoke the private IsValidAnydeskId method
+ ///
+ private bool InvokeIsValidAnydeskId(string anydeskId)
+ {
+ var method = typeof(ProtocolAnyDesk).GetMethod("IsValidAnydeskId",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+
+ if (method == null)
+ {
+ throw new Exception("IsValidAnydeskId method not found. The method may have been renamed or removed.");
+ }
+
+ return (bool)method.Invoke(_protocolAnydesk, new object[] { anydeskId });
+ }
+
+ #endregion
+}
From 208ce663b275ca8eebed59da30fa1ced979a84a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 13:08:50 +0000
Subject: [PATCH 04/10] Address code review feedback - improve security
validations
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
---
.../Protocol/AnyDesk/ProtocolAnyDesk.cs | 2 +-
mRemoteNG/UI/Forms/FrmAbout.cs | 23 +++++++++++++++----
.../Forms/OptionsPages/NotificationsPage.cs | 5 ++--
3 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
index 3206d5b8f..b88ab4853 100644
--- a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
+++ b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
@@ -221,7 +221,7 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
return false;
}
- string arguments = $"{anydeskId}";
+ string arguments = anydeskId;
// Add --with-password flag if password is provided
bool hasPassword = !string.IsNullOrEmpty(_connectionInfo.Password);
diff --git a/mRemoteNG/UI/Forms/FrmAbout.cs b/mRemoteNG/UI/Forms/FrmAbout.cs
index 969c1879a..0f0f8c92d 100644
--- a/mRemoteNG/UI/Forms/FrmAbout.cs
+++ b/mRemoteNG/UI/Forms/FrmAbout.cs
@@ -1,6 +1,8 @@
using System.Diagnostics;
using System.Windows.Forms;
+using mRemoteNG.App;
using mRemoteNG.App.Info;
+using mRemoteNG.Messages;
using mRemoteNG.Themes;
using mRemoteNG.Resources.Language;
using System.Reflection;
@@ -77,6 +79,19 @@ namespace mRemoteNG.UI.Forms
private void OpenUrl(string url)
{
+ // Validate URL format to prevent injection
+ if (string.IsNullOrWhiteSpace(url))
+ return;
+
+ // Basic URL validation - ensure it starts with http:// or https://
+ if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
+ !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
+ {
+ Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
+ $"Invalid URL format: {url}", true);
+ return;
+ }
+
try
{
// Try to open URL with UseShellExecute
@@ -92,15 +107,15 @@ namespace mRemoteNG.UI.Forms
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- // Use ArgumentList for better security instead of string concatenation
+ // Use rundll32 with url.dll,FileProtocolHandler as a safer alternative to cmd /c start
+ // This is the recommended Windows approach for opening URLs
var startInfo = new ProcessStartInfo
{
- FileName = "cmd",
+ FileName = "rundll32.exe",
UseShellExecute = false,
CreateNoWindow = true
};
- startInfo.ArgumentList.Add("/c");
- startInfo.ArgumentList.Add("start");
+ startInfo.ArgumentList.Add("url.dll,FileProtocolHandler");
startInfo.ArgumentList.Add(url);
Process.Start(startInfo);
}
diff --git a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
index 5bda1518a..82ff55f79 100644
--- a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
+++ b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
@@ -407,13 +407,14 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// when all fails open filelocation to logfile...
// Open Windows Explorer to the directory containing the file
- // Use ArgumentList for better security
+ // Use ArgumentList for better security with separate arguments
var startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
UseShellExecute = false
};
- startInfo.ArgumentList.Add("/select," + path);
+ startInfo.ArgumentList.Add("/select,");
+ startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
return true;
}
From 469ca4859272773b7edbe60ff213f6b62912b0c5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 13:12:15 +0000
Subject: [PATCH 05/10] Fix compilation issues and improve code review feedback
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
---
mRemoteNG/UI/Forms/FrmAbout.cs | 78 ++++++++++---------
.../Forms/OptionsPages/NotificationsPage.cs | 12 +--
2 files changed, 46 insertions(+), 44 deletions(-)
diff --git a/mRemoteNG/UI/Forms/FrmAbout.cs b/mRemoteNG/UI/Forms/FrmAbout.cs
index 0f0f8c92d..d11220e8a 100644
--- a/mRemoteNG/UI/Forms/FrmAbout.cs
+++ b/mRemoteNG/UI/Forms/FrmAbout.cs
@@ -1,8 +1,7 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
using System.Windows.Forms;
-using mRemoteNG.App;
using mRemoteNG.App.Info;
-using mRemoteNG.Messages;
using mRemoteNG.Themes;
using mRemoteNG.Resources.Language;
using System.Reflection;
@@ -87,14 +86,14 @@ namespace mRemoteNG.UI.Forms
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
- Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
- $"Invalid URL format: {url}", true);
+ // Invalid URL format - don't try to open it
return;
}
try
{
- // Try to open URL with UseShellExecute
+ // Use the standard .NET approach for opening URLs securely
+ // UseShellExecute=true delegates to the OS default handler
var startInfo = new ProcessStartInfo
{
FileName = url,
@@ -104,44 +103,47 @@ namespace mRemoteNG.UI.Forms
}
catch
{
- // hack because of this: https://github.com/dotnet/corefx/issues/10361
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ // Fallback for older .NET Core versions with bug: https://github.com/dotnet/corefx/issues/10361
+ // Use platform-specific URL launchers
+ try
{
- // Use rundll32 with url.dll,FileProtocolHandler as a safer alternative to cmd /c start
- // This is the recommended Windows approach for opening URLs
- var startInfo = new ProcessStartInfo
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- FileName = "rundll32.exe",
- UseShellExecute = false,
- CreateNoWindow = true
- };
- startInfo.ArgumentList.Add("url.dll,FileProtocolHandler");
- startInfo.ArgumentList.Add(url);
- Process.Start(startInfo);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- var startInfo = new ProcessStartInfo
+ // Use rundll32 with url.dll as fallback
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "rundll32.exe",
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ startInfo.ArgumentList.Add("url.dll,FileProtocolHandler");
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- FileName = "xdg-open",
- UseShellExecute = false
- };
- startInfo.ArgumentList.Add(url);
- Process.Start(startInfo);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- var startInfo = new ProcessStartInfo
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "xdg-open",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- FileName = "open",
- UseShellExecute = false
- };
- startInfo.ArgumentList.Add(url);
- Process.Start(startInfo);
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "open",
+ UseShellExecute = false
+ };
+ startInfo.ArgumentList.Add(url);
+ Process.Start(startInfo);
+ }
}
- else
+ catch
{
- throw;
+ // Unable to open URL - silently fail
}
}
}
diff --git a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
index 82ff55f79..d9ce9ddff 100644
--- a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
+++ b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
@@ -7,6 +7,7 @@ using mRemoteNG.App;
using mRemoteNG.Config.Settings.Registry;
using mRemoteNG.Properties;
using mRemoteNG.Resources.Language;
+using mRemoteNG.Tools;
namespace mRemoteNG.UI.Forms.OptionsPages
{
@@ -343,7 +344,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
try
{
// Validate path to prevent command injection
- Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+ PathValidator.ValidatePathOrThrow(path, nameof(path));
// Open the file using the default application associated with its file type based on the user's preference
// Use ProcessStartInfo with UseShellExecute for better control
@@ -372,7 +373,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
try
{
// Validate path to prevent command injection
- Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+ PathValidator.ValidatePathOrThrow(path, nameof(path));
// Open it in "Notepad" (Windows default editor).
// Usually available on all Windows systems
@@ -403,18 +404,17 @@ namespace mRemoteNG.UI.Forms.OptionsPages
try
{
// Validate path to prevent command injection
- Tools.PathValidator.ValidatePathOrThrow(path, nameof(path));
+ PathValidator.ValidatePathOrThrow(path, nameof(path));
// when all fails open filelocation to logfile...
// Open Windows Explorer to the directory containing the file
- // Use ArgumentList for better security with separate arguments
+ // Explorer expects /select,"path" as a single argument
var startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
UseShellExecute = false
};
- startInfo.ArgumentList.Add("/select,");
- startInfo.ArgumentList.Add(path);
+ startInfo.ArgumentList.Add($"/select,\"{path}\"");
Process.Start(startInfo);
return true;
}
From ba97933f3391c7b2b02fdfa96f8be9ac6b7c25e7 Mon Sep 17 00:00:00 2001
From: Dimitrij
Date: Tue, 10 Feb 2026 21:08:46 +0000
Subject: [PATCH 06/10] Update
mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
index d9ce9ddff..4721c1387 100644
--- a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
+++ b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
@@ -414,7 +414,8 @@ namespace mRemoteNG.UI.Forms.OptionsPages
FileName = "explorer.exe",
UseShellExecute = false
};
- startInfo.ArgumentList.Add($"/select,\"{path}\"");
+ startInfo.ArgumentList.Add("/select,");
+ startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
return true;
}
From 380e91de076fb4f23b6b38d6fc310c7bab1a5f7a Mon Sep 17 00:00:00 2001
From: Dimitrij
Date: Tue, 10 Feb 2026 21:09:06 +0000
Subject: [PATCH 07/10] Update
mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
index b88ab4853..26167c79b 100644
--- a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
+++ b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
@@ -217,7 +217,7 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
if (!IsValidAnydeskId(anydeskId))
{
Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
- "Invalid AnyDesk ID format. Only alphanumeric characters, @, -, and _ are allowed.", true);
+ "Invalid AnyDesk ID format. Only alphanumeric characters, @, -, _, and . are allowed.", true);
return false;
}
From 50de37c3a421039e3c9fcb9762cdcf0baddc5eb6 Mon Sep 17 00:00:00 2001
From: Dimitrij
Date: Tue, 10 Feb 2026 21:09:56 +0000
Subject: [PATCH 08/10] Update
mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
index 4721c1387..2c1948729 100644
--- a/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
+++ b/mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
@@ -417,8 +417,8 @@ namespace mRemoteNG.UI.Forms.OptionsPages
startInfo.ArgumentList.Add("/select,");
startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
- return true;
- }
+ return true;
+ }
catch
{
// If necessary, the error can be logged here.
From 173b208eb16acb635331848d15617c1ca75aa262 Mon Sep 17 00:00:00 2001
From: Dimitrij
Date: Tue, 10 Feb 2026 21:10:12 +0000
Subject: [PATCH 09/10] Update mRemoteNG/UI/Forms/FrmAbout.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
mRemoteNG/UI/Forms/FrmAbout.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/mRemoteNG/UI/Forms/FrmAbout.cs b/mRemoteNG/UI/Forms/FrmAbout.cs
index d11220e8a..a884c35ed 100644
--- a/mRemoteNG/UI/Forms/FrmAbout.cs
+++ b/mRemoteNG/UI/Forms/FrmAbout.cs
@@ -143,7 +143,9 @@ namespace mRemoteNG.UI.Forms
}
catch
{
- // Unable to open URL - silently fail
+ // Unable to open URL - notify the user
+ Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
+ "Unable to open URL in browser. Please open manually: " + url, true);
}
}
}
From 91c7df22b22c6ef712cc2130c3d6217c9ea2690a Mon Sep 17 00:00:00 2001
From: Dimitrij
Date: Tue, 10 Feb 2026 21:10:35 +0000
Subject: [PATCH 10/10] Update
mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../Connection/Protocol/ProtocolAnydeskTests.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs b/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
index 9582846d2..0fe849d33 100644
--- a/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
+++ b/mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
@@ -6,23 +6,23 @@ using NUnit.Framework;
namespace mRemoteNGTests.Connection.Protocol;
-public class ProtocolAnydeskTests
+public class ProtocolAnyDeskTests
{
- private ProtocolAnyDesk _protocolAnydesk;
+ private ProtocolAnyDesk _protocolAnyDesk;
private ConnectionInfo _connectionInfo;
[SetUp]
public void Setup()
{
_connectionInfo = new ConnectionInfo();
- _protocolAnydesk = new ProtocolAnyDesk(_connectionInfo);
+ _protocolAnyDesk = new ProtocolAnyDesk(_connectionInfo);
}
[TearDown]
public void Teardown()
{
- _protocolAnydesk?.Close();
- _protocolAnydesk = null;
+ _protocolAnyDesk?.Close();
+ _protocolAnyDesk = null;
_connectionInfo = null;
}