diff --git a/rules/dist/cpe-mapping-seed.json b/rules/dist/cpe-mapping-seed.json index abc0f454..e829040b 100644 --- a/rules/dist/cpe-mapping-seed.json +++ b/rules/dist/cpe-mapping-seed.json @@ -15,7 +15,8 @@ { "normalizedVendor": "microsoft", "normalizedProduct": "microsoft edge", - "displayNamePatterns": ["microsoft edge", "edge", "microsoft edge webview2", "webview2 runtime"], + "displayNamePatterns": ["microsoft edge", "microsoft edge webview2", "webview2 runtime"], + "excludePatterns": ["edge update"], "publisherPatterns": ["microsoft", "microsoft corporation"], "cpeVendor": "microsoft", "cpeProduct": "edge_chromium", diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 227b54a0..25e1ae45 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -802 +803 diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityCorrelationServiceTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityCorrelationServiceTests.cs index 06266716..53243da0 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityCorrelationServiceTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/VulnerabilityCorrelationServiceTests.cs @@ -544,6 +544,50 @@ public class VulnerabilityCorrelationServiceTests Assert.Equal("cpe:2.3:a:cisco:secure_client", result!.CpeUri); } + [Fact] + public void FindCpeMapping_ExcludePattern_PreventsMatch() + { + // "Microsoft Edge Update" should NOT match edge_chromium because of excludePatterns + var svc = CreateServiceWithMappings(new List + { + new CpeMappingEntry + { + NormalizedVendor = "microsoft", + NormalizedProduct = "microsoft edge", + DisplayNamePatterns = new List { "microsoft edge" }, + ExcludePatterns = new List { "edge update" }, + CpeUri = "cpe:2.3:a:microsoft:edge_chromium", + CpeProduct = "edge_chromium" + } + }); + + // "Microsoft Edge Update" contains "microsoft edge" but also "edge update" → excluded + var result = svc.FindCpeMapping("microsoft", "microsoft edge update", "Microsoft Edge Update"); + Assert.Null(result); + } + + [Fact] + public void FindCpeMapping_ExcludePattern_DoesNotAffectLegitMatch() + { + var svc = CreateServiceWithMappings(new List + { + new CpeMappingEntry + { + NormalizedVendor = "microsoft", + NormalizedProduct = "microsoft edge", + DisplayNamePatterns = new List { "microsoft edge" }, + ExcludePatterns = new List { "edge update" }, + CpeUri = "cpe:2.3:a:microsoft:edge_chromium", + CpeProduct = "edge_chromium" + } + }); + + // Real Edge browser → should still match (no exclude pattern hit) + var result = svc.FindCpeMapping("microsoft", "microsoft edge", "Microsoft Edge"); + Assert.NotNull(result); + Assert.Equal("high", result!.MatchConfidence); // Exact match on normalizedProduct + } + // ----------------------------------------------------------------------- // MergeReports // ----------------------------------------------------------------------- diff --git a/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/VulnerabilityCorrelationService.cs b/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/VulnerabilityCorrelationService.cs index 8710ca82..767359e0 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/VulnerabilityCorrelationService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/VulnerabilityCorrelationService.cs @@ -278,9 +278,11 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability // 2. Pattern match on displayNamePatterns var displayNameLower = (displayName ?? "").ToLowerInvariant(); + var normalizedNameLower = (normalizedName ?? "").ToLowerInvariant(); var patternMatch = _cpeMappings.FirstOrDefault(m => m.DisplayNamePatterns != null && - m.DisplayNamePatterns.Any(p => displayNameLower.Contains(p.ToLowerInvariant()))); + m.DisplayNamePatterns.Any(p => displayNameLower.Contains(p.ToLowerInvariant())) && + !IsExcluded(m, displayNameLower, normalizedNameLower)); if (patternMatch != null) { patternMatch.MatchConfidence = "medium"; @@ -294,7 +296,8 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability m.PublisherPatterns != null && m.PublisherPatterns.Any(p => string.Equals(p, normalizedVendor, StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(normalizedName) && - normalizedName.Contains(m.CpeProduct?.Replace("_", " ") ?? "", StringComparison.OrdinalIgnoreCase)); + normalizedName.Contains(m.CpeProduct?.Replace("_", " ") ?? "", StringComparison.OrdinalIgnoreCase) && + !IsExcluded(m, displayNameLower, normalizedNameLower)); if (vendorMatch != null) { vendorMatch.MatchConfidence = "medium"; @@ -305,6 +308,24 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability return null; } + /// + /// Returns true if the mapping's ExcludePatterns match the display name or normalized name. + /// Used to prevent false-positive CPE matches (e.g. "Microsoft Edge Update" ≠ Edge browser). + /// + private static bool IsExcluded(CpeMappingEntry mapping, string displayNameLower, string normalizedNameLower) + { + if (mapping.ExcludePatterns == null || mapping.ExcludePatterns.Count == 0) + return false; + + foreach (var pattern in mapping.ExcludePatterns) + { + var p = pattern.ToLowerInvariant(); + if (displayNameLower.Contains(p) || normalizedNameLower.Contains(p)) + return true; + } + return false; + } + // ----------------------------------------------------------------------- // CVE Lookup (with Table Storage caching via IVulnerabilityRepository) // ----------------------------------------------------------------------- @@ -1043,6 +1064,7 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability public string? NormalizedVendor { get; set; } public string? NormalizedProduct { get; set; } public List DisplayNamePatterns { get; set; } = new List(); + public List ExcludePatterns { get; set; } = new List(); public List PublisherPatterns { get; set; } = new List(); public string? CpeVendor { get; set; } public string? CpeProduct { get; set; }