using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; namespace BizTalkSapEnvironmentInventory.Infrastructure { /// /// Zentraler Schutz gegen die Aufnahme von Kennwörtern, Tokens und Schlüsseldaten in Log oder DOCX. /// internal static class SensitiveDataSanitizer { private static readonly string[] SensitiveFragments = { "password", "passwd", "passphrase", "pwd", "secret", "token", "privatekey", "clientsecret", "accesskey", "apikey", "connectionstring" }; private static readonly Regex UriSecretPattern = new Regex( @"(?i)(password|passwd|passphrase|pwd|secret|token|clientsecret)\s*=\s*(?:""[^""]*""|'[^']*'|[^;&\s""']+)", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex UserInfoPattern = new Regex( @"(?i)([a-z][a-z0-9+.-]*://)([^/@:\s]+):([^/@\s]+)@", RegexOptions.Compiled | RegexOptions.CultureInvariant); /// /// Erkennt sensitive Feldnamen unabhängig von Großschreibung und Trennzeichen. /// public static bool IsSensitiveName(string name) { var normalized = (name ?? string.Empty).Replace("_", string.Empty) .Replace("-", string.Empty); return SensitiveFragments.Any(fragment => normalized.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0); } /// /// Redigiert einen Wert anhand seines semantischen Feldnamens. /// public static string RedactValue(string name, string value) { if (IsSensitiveName(name)) { return string.IsNullOrWhiteSpace(value) ? "Nicht gesetzt" : "Konfiguriert (Wert nicht dokumentiert)"; } return SanitizeText(value); } /// /// Entfernt Secrets aus URI-/Key-Value-Texten und eingebetteten Benutzerinformationen. /// public static string SanitizeText(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } var sanitized = UriSecretPattern.Replace( value, match => match.Groups[1].Value + "=***REDACTED***"); sanitized = UserInfoPattern.Replace( sanitized, match => match.Groups[1].Value + match.Groups[2].Value + ":***REDACTED***@"); return sanitized; } /// /// Erstellt eine secret-bereinigte Kopie eines XML-Teilbaums. /// public static XElement SanitizeXml(XElement source) { if (source == null) { return null; } if (IsSensitiveName(source.Name.LocalName) || IsCipherElement(source.Name.LocalName)) { return new XElement(source.Name, "***REDACTED***"); } var result = new XElement(source.Name); foreach (var attribute in source.Attributes()) { result.Add(new XAttribute( attribute.Name, IsSensitiveName(attribute.Name.LocalName) ? "***REDACTED***" : SanitizeText(attribute.Value))); } foreach (var node in source.Nodes()) { var element = node as XElement; if (element != null) { result.Add(SanitizeXml(element)); continue; } var text = node as XText; if (text != null) { result.Add(new XText(SanitizeText(text.Value))); } } return result; } private static bool IsCipherElement(string name) { return string.Equals(name, "EncryptedData", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "CipherData", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "CipherValue", StringComparison.OrdinalIgnoreCase); } } }