From 1e3cf0ad2ac0ff297b1283d69923a3da4788fc72 Mon Sep 17 00:00:00 2001 From: Bert Temme Date: Tue, 11 Aug 2026 10:03:15 +0200 Subject: [PATCH] sanitize connection string for logging --- .../CommandLineArgumentSanitizerTests.cs | 50 +++++++++++++++++++ src/pg2b3dm/CommandLineArgumentSanitizer.cs | 16 ++++++ src/pg2b3dm/ConnectionStringResolver.cs | 2 +- src/pg2b3dm/Program.cs | 2 +- 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/pg2b3dm.database.tests/CommandLineArgumentSanitizerTests.cs create mode 100644 src/pg2b3dm/CommandLineArgumentSanitizer.cs diff --git a/src/pg2b3dm.database.tests/CommandLineArgumentSanitizerTests.cs b/src/pg2b3dm.database.tests/CommandLineArgumentSanitizerTests.cs new file mode 100644 index 00000000..8eead071 --- /dev/null +++ b/src/pg2b3dm.database.tests/CommandLineArgumentSanitizerTests.cs @@ -0,0 +1,50 @@ +namespace pg2b3dm.database.tests; + +public class CommandLineArgumentSanitizerTests +{ + [Test] + public void SanitizeForLogging_RedactsPasswordInConnectionString() + { + var args = new[] + { + "--connection", + "Host=db.example;Username=alice;Password=secret;Database=gis", + "-t", + "public.buildings" + }; + + var sanitized = CommandLineArgumentSanitizer.SanitizeForLogging(args); + + Assert.That(sanitized, Is.EqualTo("--connection Host=db.example;Username=alice;Password=***;Database=gis -t public.buildings")); + } + + [Test] + public void SanitizeForLogging_RedactsPwdAliasCaseInsensitively() + { + var args = new[] + { + "--connection", + "Host=db.example;Username=alice;PWD = secret;Database=gis" + }; + + var sanitized = CommandLineArgumentSanitizer.SanitizeForLogging(args); + + Assert.That(sanitized, Is.EqualTo("--connection Host=db.example;Username=alice;PWD = ***;Database=gis")); + } + + [Test] + public void SanitizeForLogging_LeavesNonPasswordArgumentsUnchanged() + { + var args = new[] + { + "--host", + "localhost", + "--dbname", + "gis" + }; + + var sanitized = CommandLineArgumentSanitizer.SanitizeForLogging(args); + + Assert.That(sanitized, Is.EqualTo("--host localhost --dbname gis")); + } +} diff --git a/src/pg2b3dm/CommandLineArgumentSanitizer.cs b/src/pg2b3dm/CommandLineArgumentSanitizer.cs new file mode 100644 index 00000000..fa3216ff --- /dev/null +++ b/src/pg2b3dm/CommandLineArgumentSanitizer.cs @@ -0,0 +1,16 @@ +using System.Linq; +using System.Text.RegularExpressions; + +namespace pg2b3dm; + +public static partial class CommandLineArgumentSanitizer +{ + public static string SanitizeForLogging(string[] args) + => string.Join(" ", args.Select(Redact)); + + private static string Redact(string arg) + => PasswordRegex().Replace(arg, "$1***"); + + [GeneratedRegex(@"(\b(?:password|pwd)\s*=\s*)[^;]*", RegexOptions.IgnoreCase)] + private static partial Regex PasswordRegex(); +} diff --git a/src/pg2b3dm/ConnectionStringResolver.cs b/src/pg2b3dm/ConnectionStringResolver.cs index 5cf1ce47..49c8d2c4 100644 --- a/src/pg2b3dm/ConnectionStringResolver.cs +++ b/src/pg2b3dm/ConnectionStringResolver.cs @@ -11,7 +11,7 @@ public static class ConnectionStringResolver { private const string DefaultHost = "localhost"; private const string DefaultPort = "5432"; - private const string ExampleCommand = "pg2b3dm --connection \"Host=localhost;Username=postgres;Database=postgres;Ssl Mode=Require;CommandTimeOut=0\" -t my_schema.my_table"; + private const string ExampleCommand = "pg2b3dm --connection \"Host=localhost;Username=postgres;Database=postgres;CommandTimeOut=0\" -t my_schema.my_table"; private static readonly string[] DeprecatedParameterOrder = ["--username", "--host", "--dbname", "--port"]; private static readonly Dictionary DeprecatedParameterAliases = new(StringComparer.OrdinalIgnoreCase) { diff --git a/src/pg2b3dm/Program.cs b/src/pg2b3dm/Program.cs index 445e3dbd..ccbcf3e0 100644 --- a/src/pg2b3dm/Program.cs +++ b/src/pg2b3dm/Program.cs @@ -20,7 +20,7 @@ static void Main(string[] args) { var version = Assembly.GetEntryAssembly().GetName().Version; Console.WriteLine($"Tool: pg2b3dm {version}"); - Console.WriteLine("Options: " + string.Join(" ", args)); + Console.WriteLine("Options: " + CommandLineArgumentSanitizer.SanitizeForLogging(args)); Parser.Default.ParseArguments(args).WithParsed(o => { // Octree checks if(o.subdivisionScheme == SubdivisionScheme.OCTREE) {