Initial commit: BizTalk SAP environment inventory

This commit is contained in:
2026-07-27 14:21:50 +02:00
commit 439427cc17
26 changed files with 5281 additions and 0 deletions
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace BizTalkSapEnvironmentInventory.Infrastructure
{
/// <summary>
/// Zentraler Schutz gegen die Aufnahme von Kennwörtern, Tokens und Schlüsseldaten in Log oder DOCX.
/// </summary>
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);
/// <summary>
/// Erkennt sensitive Feldnamen unabhängig von Großschreibung und Trennzeichen.
/// </summary>
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);
}
/// <summary>
/// Redigiert einen Wert anhand seines semantischen Feldnamens.
/// </summary>
public static string RedactValue(string name, string value)
{
if (IsSensitiveName(name))
{
return string.IsNullOrWhiteSpace(value)
? "Nicht gesetzt"
: "Konfiguriert (Wert nicht dokumentiert)";
}
return SanitizeText(value);
}
/// <summary>
/// Entfernt Secrets aus URI-/Key-Value-Texten und eingebetteten Benutzerinformationen.
/// </summary>
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;
}
/// <summary>
/// Erstellt eine secret-bereinigte Kopie eines XML-Teilbaums.
/// </summary>
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);
}
}
}