From 06ee261de4af963961561539250a5dd787674fe1 Mon Sep 17 00:00:00 2001 From: Francesco Dipietromaria Date: Fri, 7 Aug 2026 00:33:21 +0200 Subject: [PATCH 1/3] Bump version to 1.0.25, harden CSV export against formula injection, and update gitignore --- .gitignore | 3 +++ src/MITMPulse/MITMPulse.csproj | 6 +++--- src/MITMPulse/Services/HistoryService.cs | 14 +++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index a354f6e..66629db 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ $RECYCLE.BIN/ # Project private instructions MITMPulse_project.md + +# Claude Code local settings +.claude/ diff --git a/src/MITMPulse/MITMPulse.csproj b/src/MITMPulse/MITMPulse.csproj index 776133d..6f35ead 100644 --- a/src/MITMPulse/MITMPulse.csproj +++ b/src/MITMPulse/MITMPulse.csproj @@ -13,9 +13,9 @@ Francesco Dipietromaria dpmworld.net Copyright © 2026 Francesco Dipietromaria (www.dpmworld.net) - 1.0.24 - 1.0.24.0 - 1.0.24.0 + 1.0.25 + 1.0.25.0 + 1.0.25.0 Assets\icon.ico diff --git a/src/MITMPulse/Services/HistoryService.cs b/src/MITMPulse/Services/HistoryService.cs index 4b5f158..61c51fa 100644 --- a/src/MITMPulse/Services/HistoryService.cs +++ b/src/MITMPulse/Services/HistoryService.cs @@ -116,5 +116,17 @@ public async Task ExportToCsvAsync(string filePath, CancellationToken cancellati await File.WriteAllTextAsync(filePath, sb.ToString(), Encoding.UTF8, cancellationToken); } - private static string EscapeCsv(string input) => input.Replace("\"", "\"\""); + private static string EscapeCsv(string input) + { + string escaped = input.Replace("\"", "\"\""); + + // Prevent CSV/formula injection: neutralize values that Excel/LibreOffice + // may interpret as formulas (e.g. a malicious certificate Issuer field). + if (escaped.Length > 0 && (escaped[0] == '=' || escaped[0] == '+' || escaped[0] == '-' || escaped[0] == '@' || escaped[0] == '\t' || escaped[0] == '\r')) + { + escaped = "'" + escaped; + } + + return escaped; + } } From 08e6e5b89f00511e83a3068a32e6cfca891143af Mon Sep 17 00:00:00 2001 From: Francesco Dipietromaria Date: Mon, 17 Aug 2026 18:19:15 +0200 Subject: [PATCH 2/3] Add DTLS over UDP inspection, top status badge, window drag handling, and bump version to 1.0.26 --- src/MITMPulse/MITMPulse.csproj | 6 +- src/MITMPulse/MainWindow.xaml | 65 +++++-- src/MITMPulse/MainWindow.xaml.cs | 16 ++ .../Models/InspectionHistoryEntry.cs | 1 + src/MITMPulse/Models/SslInspectionResult.cs | 2 + src/MITMPulse/Services/HistoryService.cs | 7 +- .../Services/SslInspectionService.cs | 184 ++++++++++++++++++ src/MITMPulse/ViewModels/MainViewModel.cs | 3 + .../Services/SslInspectionServiceTests.cs | 30 +++ .../ViewModels/MainViewModelTests.cs | 22 +++ 10 files changed, 312 insertions(+), 24 deletions(-) diff --git a/src/MITMPulse/MITMPulse.csproj b/src/MITMPulse/MITMPulse.csproj index 6f35ead..022e325 100644 --- a/src/MITMPulse/MITMPulse.csproj +++ b/src/MITMPulse/MITMPulse.csproj @@ -13,9 +13,9 @@ Francesco Dipietromaria dpmworld.net Copyright © 2026 Francesco Dipietromaria (www.dpmworld.net) - 1.0.25 - 1.0.25.0 - 1.0.25.0 + 1.0.26 + 1.0.26.0 + 1.0.26.0 Assets\icon.ico diff --git a/src/MITMPulse/MainWindow.xaml b/src/MITMPulse/MainWindow.xaml index 8b840c7..be82551 100644 --- a/src/MITMPulse/MainWindow.xaml +++ b/src/MITMPulse/MainWindow.xaml @@ -9,6 +9,10 @@ WindowStartupLocation="CenterScreen" Icon="pack://application:,,,/Assets/icon.ico"> + + + + @@ -19,7 +23,8 @@ - @@ -91,28 +96,52 @@ - - + + + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + diff --git a/src/MITMPulse/MainWindow.xaml.cs b/src/MITMPulse/MainWindow.xaml.cs index 1dd6dbc..45cfba8 100644 --- a/src/MITMPulse/MainWindow.xaml.cs +++ b/src/MITMPulse/MainWindow.xaml.cs @@ -9,6 +9,22 @@ public MainWindow() InitializeComponent(); DataContext = App.ViewModel; Loaded += MainWindow_Loaded; + MouseDown += MainWindow_MouseDown; + AppTitleBar.MouseLeftButtonDown += (s, e) => + { + if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) + { + DragMove(); + } + }; + } + + private void MainWindow_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (e.ChangedButton == System.Windows.Input.MouseButton.Left && e.GetPosition(this).Y < 40) + { + DragMove(); + } } private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e) diff --git a/src/MITMPulse/Models/InspectionHistoryEntry.cs b/src/MITMPulse/Models/InspectionHistoryEntry.cs index 6dab840..46fd3b5 100644 --- a/src/MITMPulse/Models/InspectionHistoryEntry.cs +++ b/src/MITMPulse/Models/InspectionHistoryEntry.cs @@ -18,4 +18,5 @@ public class InspectionHistoryEntry public string StatusSummary { get; set; } = string.Empty; public string ServerIssuer { get; set; } = string.Empty; public string ServerThumbprint { get; set; } = string.Empty; + public bool IsDtlsSupported { get; set; } } diff --git a/src/MITMPulse/Models/SslInspectionResult.cs b/src/MITMPulse/Models/SslInspectionResult.cs index 2962528..4d05b9c 100644 --- a/src/MITMPulse/Models/SslInspectionResult.cs +++ b/src/MITMPulse/Models/SslInspectionResult.cs @@ -24,4 +24,6 @@ public class SslInspectionResult public string PacScriptUrl { get; set; } = string.Empty; public string PacResolvedProxy { get; set; } = string.Empty; public string TunnelStatus { get; set; } = string.Empty; + public bool IsDtlsSupported { get; set; } + public string DtlsDetails { get; set; } = string.Empty; } diff --git a/src/MITMPulse/Services/HistoryService.cs b/src/MITMPulse/Services/HistoryService.cs index 61c51fa..e452e4e 100644 --- a/src/MITMPulse/Services/HistoryService.cs +++ b/src/MITMPulse/Services/HistoryService.cs @@ -71,7 +71,8 @@ public async Task SaveEntryAsync(SslInspectionResult result, CancellationToken c IsSuccess = result.IsSuccess, StatusSummary = statusSummary, ServerIssuer = result.ServerCertificate?.Issuer ?? string.Empty, - ServerThumbprint = result.ServerCertificate?.Thumbprint ?? string.Empty + ServerThumbprint = result.ServerCertificate?.Thumbprint ?? string.Empty, + IsDtlsSupported = result.IsDtlsSupported }; history.Insert(0, entry); // Add newest at top @@ -106,11 +107,11 @@ public async Task ExportToCsvAsync(string filePath, CancellationToken cancellati { var history = await GetHistoryAsync(cancellationToken); var sb = new StringBuilder(); - sb.AppendLine("Timestamp,TargetHost,TargetPort,IsSuccess,IsSslInspectionDetected,TlsVersion,CipherSuite,ServerIssuer,ServerThumbprint,StatusSummary"); + sb.AppendLine("Timestamp,TargetHost,TargetPort,IsSuccess,IsSslInspectionDetected,IsDtlsSupported,TlsVersion,CipherSuite,ServerIssuer,ServerThumbprint,StatusSummary"); foreach (var item in history) { - sb.AppendLine($"\"{item.Timestamp:yyyy-MM-dd HH:mm:ss}\",\"{EscapeCsv(item.TargetHost)}\",{item.TargetPort},{item.IsSuccess},{item.IsSslInspectionDetected},\"{EscapeCsv(item.TlsVersion)}\",\"{EscapeCsv(item.CipherSuite)}\",\"{EscapeCsv(item.ServerIssuer)}\",\"{EscapeCsv(item.ServerThumbprint)}\",\"{EscapeCsv(item.StatusSummary)}\""); + sb.AppendLine($"\"{item.Timestamp:yyyy-MM-dd HH:mm:ss}\",\"{EscapeCsv(item.TargetHost)}\",{item.TargetPort},{item.IsSuccess},{item.IsSslInspectionDetected},{item.IsDtlsSupported},\"{EscapeCsv(item.TlsVersion)}\",\"{EscapeCsv(item.CipherSuite)}\",\"{EscapeCsv(item.ServerIssuer)}\",\"{EscapeCsv(item.ServerThumbprint)}\",\"{EscapeCsv(item.StatusSummary)}\""); } await File.WriteAllTextAsync(filePath, sb.ToString(), Encoding.UTF8, cancellationToken); diff --git a/src/MITMPulse/Services/SslInspectionService.cs b/src/MITMPulse/Services/SslInspectionService.cs index df2c01c..4671d98 100644 --- a/src/MITMPulse/Services/SslInspectionService.cs +++ b/src/MITMPulse/Services/SslInspectionService.cs @@ -41,6 +41,9 @@ public async Task InspectEndpointAsync( return result; } + // Launch DTLS over UDP test in parallel (with short timeout) + var dtlsTask = TestDtlsOverUdpAsync(targetHost, targetPort, cancellationToken); + try { using var tcpClient = new TcpClient(); @@ -170,6 +173,20 @@ public async Task InspectEndpointAsync( result.ErrorMessage = ex.Message; } + // Await DTLS UDP result (if not already completed) + try + { + result.IsDtlsSupported = await dtlsTask.ConfigureAwait(false); + if (result.IsDtlsSupported) + { + result.DtlsDetails = "DTLS over UDP (Datagram TLS) Active & Verified"; + } + } + catch + { + result.IsDtlsSupported = false; + } + return result; } @@ -495,4 +512,171 @@ private static async Task EstablishConnectionAsync( await tcpClient.ConnectAsync(targetHost, targetPort, cancellationToken).ConfigureAwait(false); return tcpClient.GetStream(); } + + /// + /// Tests if the remote endpoint speaks DTLS (Datagram Transport Layer Security) over UDP. + /// Sends a standard RFC 6347 DTLS 1.2 ClientHello datagram and checks for a valid DTLS response (e.g. HelloVerifyRequest or ServerHello). + /// + public static async Task TestDtlsOverUdpAsync(string host, int port, CancellationToken cancellationToken = default) + { + try + { + byte[] clientHello = BuildDtlsClientHello(host); + using var udpClient = new UdpClient(); + + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(2500)); + + // Resolve host IP + var addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(false); + if (addresses.Length == 0) return false; + + var targetIp = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) ?? addresses[0]; + var endpoint = new IPEndPoint(targetIp, port); + + await udpClient.SendAsync(clientHello, clientHello.Length, endpoint).ConfigureAwait(false); + + var receiveTask = udpClient.ReceiveAsync(timeoutCts.Token).AsTask(); + var completedTask = await Task.WhenAny(receiveTask, Task.Delay(2500, timeoutCts.Token)).ConfigureAwait(false); + + if (completedTask == receiveTask) + { + var response = await receiveTask.ConfigureAwait(false); + byte[] data = response.Buffer; + + if (data.Length >= 13) + { + byte contentType = data[0]; + byte versionMajor = data[1]; + + // DTLS record content types: 22 (Handshake), 21 (Alert) + // DTLS protocol version major is 0xFE (DTLS 1.0 = 0xFEFF, DTLS 1.2 = 0xFEFD, DTLS 1.3 = 0xFEFC) + if ((contentType == 22 || contentType == 21) && versionMajor == 0xFE) + { + return true; + } + } + } + } + catch + { + // UDP/DTLS check is non-fatal: return false on timeout/error + } + + return false; + } + + /// + /// Builds a well-formed RFC 6347 DTLS 1.2 ClientHello record datagram including SNI extension. + /// + public static byte[] BuildDtlsClientHello(string host) + { + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + + using var bodyMs = new MemoryStream(); + using var bodyBw = new BinaryWriter(bodyMs); + + // Client Version (DTLS 1.2 = 0xFEFD) + bodyBw.Write(new byte[] { 0xfe, 0xfd }); + + // Random 32 bytes + byte[] randomBytes = new byte[32]; + RandomNumberGenerator.Fill(randomBytes); + bodyBw.Write(randomBytes); + + // Session ID Length = 0 + bodyBw.Write((byte)0x00); + + // Cookie Length = 0 + bodyBw.Write((byte)0x00); + + // Cipher Suites (20 bytes = 10 suites) + byte[] cipherSuites = new byte[] + { + 0x00, 0x14, // Length: 20 bytes + 0xc0, 0x2f, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + 0xc0, 0x30, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + 0xc0, 0x13, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + 0xc0, 0x14, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + 0x00, 0x9c, // TLS_RSA_WITH_AES_128_GCM_SHA256 + 0x00, 0x9d, // TLS_RSA_WITH_AES_256_GCM_SHA384 + 0x00, 0x2f, // TLS_RSA_WITH_AES_128_CBC_SHA + 0x00, 0x35, // TLS_RSA_WITH_AES_256_CBC_SHA + 0x00, 0x0a, // TLS_RSA_WITH_3DES_EDE_CBC_SHA + 0x00, 0xff // TLS_EMPTY_RENEGOTIATION_INFO_SCSV + }; + bodyBw.Write(cipherSuites); + + // Compression Methods (1 byte length + 0x00 null) + bodyBw.Write(new byte[] { 0x01, 0x00 }); + + // Extensions (SNI) + if (!string.IsNullOrWhiteSpace(host) && !IPAddress.TryParse(host, out _)) + { + byte[] hostBytes = Encoding.UTF8.GetBytes(host); + using var extMs = new MemoryStream(); + using var extBw = new BinaryWriter(extMs); + + // Extension: Server Name Indication (0x0000) + extBw.Write(new byte[] { 0x00, 0x00 }); + int sniListLength = hostBytes.Length + 3; + int sniExtLength = sniListLength + 2; + + extBw.Write((byte)(sniExtLength >> 8)); + extBw.Write((byte)(sniExtLength & 0xFF)); + + extBw.Write((byte)(sniListLength >> 8)); + extBw.Write((byte)(sniListLength & 0xFF)); + + extBw.Write((byte)0x00); // HostName type (0) + extBw.Write((byte)(hostBytes.Length >> 8)); + extBw.Write((byte)(hostBytes.Length & 0xFF)); + extBw.Write(hostBytes); + + byte[] extData = extMs.ToArray(); + bodyBw.Write((byte)(extData.Length >> 8)); + bodyBw.Write((byte)(extData.Length & 0xFF)); + bodyBw.Write(extData); + } + + byte[] handshakeBody = bodyMs.ToArray(); + + // Handshake Header (12 bytes) + using var hsMs = new MemoryStream(); + using var hsBw = new BinaryWriter(hsMs); + hsBw.Write((byte)0x01); // HandshakeType = ClientHello (1) + + // 3 bytes length + hsBw.Write((byte)(handshakeBody.Length >> 16)); + hsBw.Write((byte)((handshakeBody.Length >> 8) & 0xFF)); + hsBw.Write((byte)(handshakeBody.Length & 0xFF)); + + // 2 bytes message_seq = 0 + hsBw.Write(new byte[] { 0x00, 0x00 }); + + // 3 bytes fragment_offset = 0 + hsBw.Write(new byte[] { 0x00, 0x00, 0x00 }); + + // 3 bytes fragment_length + hsBw.Write((byte)(handshakeBody.Length >> 16)); + hsBw.Write((byte)((handshakeBody.Length >> 8) & 0xFF)); + hsBw.Write((byte)(handshakeBody.Length & 0xFF)); + + // Body + hsBw.Write(handshakeBody); + + byte[] handshakeRecord = hsMs.ToArray(); + + // DTLS Record Header (13 bytes) + bw.Write((byte)0x16); // ContentType: Handshake (22) + bw.Write(new byte[] { 0xfe, 0xfd }); // ProtocolVersion: DTLS 1.2 + bw.Write(new byte[] { 0x00, 0x00 }); // Epoch: 0 + bw.Write(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); // SequenceNumber: 0 + bw.Write((byte)(handshakeRecord.Length >> 8)); + bw.Write((byte)(handshakeRecord.Length & 0xFF)); + bw.Write(handshakeRecord); + + return ms.ToArray(); + } } diff --git a/src/MITMPulse/ViewModels/MainViewModel.cs b/src/MITMPulse/ViewModels/MainViewModel.cs index f3ebff6..84f51f7 100644 --- a/src/MITMPulse/ViewModels/MainViewModel.cs +++ b/src/MITMPulse/ViewModels/MainViewModel.cs @@ -68,6 +68,7 @@ public string AppTitleWithVersion public bool IsCertificateExpired => !IsLoading && InspectionResult != null && InspectionResult.IsSuccess && !InspectionResult.IsSslInspectionDetected && InspectionResult.ServerCertificate != null && (DateTime.UtcNow > InspectionResult.ServerCertificate.ValidTo || DateTime.UtcNow < InspectionResult.ServerCertificate.ValidFrom); public bool IsDirectConnection => !IsLoading && InspectionResult != null && InspectionResult.IsSuccess && !InspectionResult.IsSslInspectionDetected && !IsCertificateExpired; public bool IsConnectionError => !IsLoading && InspectionResult != null && !InspectionResult.IsSuccess; + public bool IsDtlsActive => !IsLoading && InspectionResult != null && InspectionResult.IsSuccess && InspectionResult.IsDtlsSupported; partial void OnInspectionResultChanged(SslInspectionResult? value) { @@ -75,6 +76,7 @@ partial void OnInspectionResultChanged(SslInspectionResult? value) OnPropertyChanged(nameof(IsCertificateExpired)); OnPropertyChanged(nameof(IsDirectConnection)); OnPropertyChanged(nameof(IsConnectionError)); + OnPropertyChanged(nameof(IsDtlsActive)); } partial void OnIsLoadingChanged(bool value) @@ -83,6 +85,7 @@ partial void OnIsLoadingChanged(bool value) OnPropertyChanged(nameof(IsCertificateExpired)); OnPropertyChanged(nameof(IsDirectConnection)); OnPropertyChanged(nameof(IsConnectionError)); + OnPropertyChanged(nameof(IsDtlsActive)); } public MainViewModel( diff --git a/tests/MITMPulse.Tests/Services/SslInspectionServiceTests.cs b/tests/MITMPulse.Tests/Services/SslInspectionServiceTests.cs index 243226e..39c63a3 100644 --- a/tests/MITMPulse.Tests/Services/SslInspectionServiceTests.cs +++ b/tests/MITMPulse.Tests/Services/SslInspectionServiceTests.cs @@ -42,4 +42,34 @@ public async Task InspectEndpointAsync_InvalidHost_ReturnsFailureResult() Assert.False(result.IsSuccess); Assert.NotEmpty(result.ErrorMessage); } + + [Fact] + public void BuildDtlsClientHello_ValidHost_ReturnsWellFormedDtlsRecord() + { + // Arrange + string host = "citrix.gateway.example.com"; + + // Act + byte[] datagram = SslInspectionService.BuildDtlsClientHello(host); + + // Assert + Assert.NotNull(datagram); + Assert.True(datagram.Length >= 13); + Assert.Equal(0x16, datagram[0]); // ContentType = Handshake (22) + Assert.Equal(0xFE, datagram[1]); // Major Version = DTLS (0xFE) + Assert.Equal(0xFD, datagram[2]); // Minor Version = DTLS 1.2 (0xFD) + } + + [Fact] + public async Task TestDtlsOverUdpAsync_InvalidHost_ReturnsFalse() + { + // Arrange + string invalidHost = "invalid.nonexistent.domain.xyz12345"; + + // Act + bool isSupported = await SslInspectionService.TestDtlsOverUdpAsync(invalidHost, 443); + + // Assert + Assert.False(isSupported); + } } diff --git a/tests/MITMPulse.Tests/ViewModels/MainViewModelTests.cs b/tests/MITMPulse.Tests/ViewModels/MainViewModelTests.cs index 6614861..1803821 100644 --- a/tests/MITMPulse.Tests/ViewModels/MainViewModelTests.cs +++ b/tests/MITMPulse.Tests/ViewModels/MainViewModelTests.cs @@ -75,4 +75,26 @@ public async Task InspectEndpointAsync_ValidHost_CallsSslInspectionService() Assert.Equal("DIRECT CONNECTION (No SSL Inspection Detected)", _sut.StatusMessage); _mockHistoryService.Verify(h => h.SaveEntryAsync(expectedResult, It.IsAny()), Times.Once); } + + [Fact] + public void Constructor_IsDtlsActive_IsFalseInitially() + { + // Assert + Assert.Null(_sut.InspectionResult); + Assert.False(_sut.IsDtlsActive); + } + + [Fact] + public void IsDtlsActive_WhenDtlsSupportedAndSuccess_ReturnsTrue() + { + // Arrange + _sut.InspectionResult = new SslInspectionResult + { + IsSuccess = true, + IsDtlsSupported = true + }; + + // Assert + Assert.True(_sut.IsDtlsActive); + } } From ef86570f07dc30688326bc67b360df99c0b0c1fa Mon Sep 17 00:00:00 2001 From: Francesco Dipietromaria Date: Mon, 17 Aug 2026 18:21:41 +0200 Subject: [PATCH 3/3] Update README with DTLS over UDP inspection and CSV security hardening features --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 75e8f3b..84034d3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ Website: [www.dpmworld.net](https://www.dpmworld.net) - 👁️ **Red Eye (`Eye24`)**: Confirmed MITM SSL Inspection active (enterprise proxy re-signing detected). - ⚠️ **Orange Warning (`Warning24`)**: Certificate expired or domain error on a public endpoint (direct connection). - 🟢 **Green Checkmark (`CheckmarkCircle24`)**: Direct secure connection verified against trusted public Root CAs. +- **DTLS over UDP Protocol Inspection**: + - Probes UDP target ports in parallel using standard RFC 6347 DTLS 1.2 `ClientHello` datagrams (with SNI). + - Displays a discrete green **DTLS** badge when DTLS transport (e.g., Citrix NetScaler Enlightened Data Transport - EDT) is verified. - **GoDaddy R1/G2 Hierarchy & Cross-Certificate Diagnostics**: - Native support for GoDaddy & Starfield Root CAs (`GoDaddy TLS Root CA - R1`, `R1v1` intermediate). - Emits specific diagnostic warnings if a NetScaler or server is missing the `R1->G2` cross-certificate, preventing client trust false positives (referencing [Go Daddy TLS Certificate not trusted](https://www.dpmworld.net/2026/07/31/go-daddy-tls-certificate-not-trusted/)). @@ -30,6 +33,7 @@ Website: [www.dpmworld.net](https://www.dpmworld.net) - **TLS Version & Cipher Suite Metrics**: Displays negotiated protocol versions (TLS 1.2, TLS 1.3) and negotiated Cipher Suites. - **Certificate Pinning**: Optional validation against expected SHA-1 certificate thumbprints with preset fallback. - **Proxy Modes & PAC/WinHTTP Tunneling**: Supports **Direct** socket connections, **System Proxy** (WinINet / System with automatic **PAC script** execution & HTTP `CONNECT` tunneling), **WinHTTP Proxy** (`netsh winhttp` P/Invoke native system proxy reader), or **Custom** explicit HTTP/SOCKS proxies with mandatory configuration validation and authentication. +- **Inspection History & Hardened CSV/JSON Export**: Stores persistent inspection records with built-in sanitization against CSV Formula Injection vulnerabilities. ### 🏢 Predefined Target Presets Pre-populated with high-priority enterprise cloud endpoints and live certificate baselines: