From 80c139136167baa8572eed31437f2b2ba7f2c6d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:26:41 +0000 Subject: [PATCH] Fix password passing issue in External Tools - Changed SetProcessProperties to use Arguments property instead of splitting by space and using ArgumentList - Added tests to verify passwords with special characters like '=' are passed correctly - This fixes the issue where passwords like 'Z-3=Wv99/Aq' were being split incorrectly Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- mRemoteNG/Tools/ExternalTool.cs | 6 +- mRemoteNGTests/Tools/ExternalToolTests.cs | 115 ++++++++++++++++++++++ 2 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 mRemoteNGTests/Tools/ExternalToolTests.cs diff --git a/mRemoteNG/Tools/ExternalTool.cs b/mRemoteNG/Tools/ExternalTool.cs index d9297e36..41c379b6 100644 --- a/mRemoteNG/Tools/ExternalTool.cs +++ b/mRemoteNG/Tools/ExternalTool.cs @@ -156,11 +156,7 @@ namespace mRemoteNG.Tools ExternalToolArgumentParser argParser = new(startConnectionInfo); process.StartInfo.UseShellExecute = true; process.StartInfo.FileName = argParser.ParseArguments(FileName); - var parsedArgs = argParser.ParseArguments(Arguments).Split(' '); - foreach (var arg in parsedArgs) - { - process.StartInfo.ArgumentList.Add(arg); - } + process.StartInfo.Arguments = argParser.ParseArguments(Arguments); if (WorkingDir != "") process.StartInfo.WorkingDirectory = argParser.ParseArguments(WorkingDir); if (RunElevated) process.StartInfo.Verb = "runas"; } diff --git a/mRemoteNGTests/Tools/ExternalToolTests.cs b/mRemoteNGTests/Tools/ExternalToolTests.cs new file mode 100644 index 00000000..7334c024 --- /dev/null +++ b/mRemoteNGTests/Tools/ExternalToolTests.cs @@ -0,0 +1,115 @@ +using System.Diagnostics; +using System.Reflection; +using mRemoteNG.Connection; +using mRemoteNG.Tools; +using NUnit.Framework; + +namespace mRemoteNGTests.Tools +{ + [TestFixture] + public class ExternalToolTests + { + [Test] + public void PasswordWithEqualsSignIsPassedCorrectly() + { + // Arrange + var connectionInfo = new ConnectionInfo + { + Password = "Z-3=Wv99/Aq", + Hostname = "testhost", + Username = "testuser" + }; + + var externalTool = new ExternalTool + { + DisplayName = "Test Tool", + FileName = "test.exe", + Arguments = "-u %USERNAME% -p %PASSWORD% -h %HOSTNAME%" + }; + + // Act + var process = new Process(); + var setProcessPropertiesMethod = typeof(ExternalTool).GetMethod( + "SetProcessProperties", + BindingFlags.NonPublic | BindingFlags.Instance + ); + setProcessPropertiesMethod?.Invoke(externalTool, new object[] { process, connectionInfo }); + + // Assert + // The arguments should contain the password with the equals sign + // It may be escaped (e.g., Z-3^=Wv99/Aq), but should not be split + Assert.That(process.StartInfo.Arguments, Does.Contain("Z-3")); + Assert.That(process.StartInfo.Arguments, Does.Contain("Wv99/Aq")); + // The equals sign should be present (possibly escaped as ^=) + Assert.That(process.StartInfo.Arguments, Does.Match("Z-3.=Wv99/Aq")); + } + + [Test] + public void PasswordWithSpecialCharactersIsPassedCorrectly() + { + // Arrange + var connectionInfo = new ConnectionInfo + { + Password = "P@ss=W0rd!", + Hostname = "testhost", + Username = "testuser" + }; + + var externalTool = new ExternalTool + { + DisplayName = "Test Tool", + FileName = "test.exe", + Arguments = "-p %PASSWORD%" + }; + + // Act + var process = new Process(); + var setProcessPropertiesMethod = typeof(ExternalTool).GetMethod( + "SetProcessProperties", + BindingFlags.NonPublic | BindingFlags.Instance + ); + setProcessPropertiesMethod?.Invoke(externalTool, new object[] { process, connectionInfo }); + + // Assert + // The password should be present in the arguments (possibly escaped) + Assert.That(process.StartInfo.Arguments, Does.Contain("P@ss")); + Assert.That(process.StartInfo.Arguments, Does.Contain("W0rd")); + } + + [Test] + public void MultipleArgumentsAreParsedCorrectly() + { + // Arrange + var connectionInfo = new ConnectionInfo + { + Password = "TestPass=123", + Hostname = "myhost.com", + Username = "admin", + Port = 8080 + }; + + var externalTool = new ExternalTool + { + DisplayName = "Test Tool", + FileName = "app.exe", + Arguments = "--host %HOSTNAME% --port %PORT% --user %USERNAME% --pass %PASSWORD%" + }; + + // Act + var process = new Process(); + var setProcessPropertiesMethod = typeof(ExternalTool).GetMethod( + "SetProcessProperties", + BindingFlags.NonPublic | BindingFlags.Instance + ); + setProcessPropertiesMethod?.Invoke(externalTool, new object[] { process, connectionInfo }); + + // Assert + var arguments = process.StartInfo.Arguments; + Assert.That(arguments, Does.Contain("myhost.com")); + Assert.That(arguments, Does.Contain("8080")); + Assert.That(arguments, Does.Contain("admin")); + Assert.That(arguments, Does.Contain("TestPass")); + Assert.That(arguments, Does.Contain("123")); + } + } +}