diff --git a/rules/dist/cpe-mapping-seed.json b/rules/dist/cpe-mapping-seed.json index e829040b..02bff4a0 100644 --- a/rules/dist/cpe-mapping-seed.json +++ b/rules/dist/cpe-mapping-seed.json @@ -179,8 +179,8 @@ "displayNamePatterns": ["microsoft visual c++ 2015-2022", "microsoft visual c++ 2015-2019", "visual c++ redistributable", "microsoft visual c++"], "publisherPatterns": ["microsoft", "microsoft corporation"], "cpeVendor": "microsoft", - "cpeProduct": "visual_c%2b%2b", - "cpeUri": "cpe:2.3:a:microsoft:visual_c%2b%2b", + "cpeProduct": "visual_c\\+\\+", + "cpeUri": "cpe:2.3:a:microsoft:visual_c\\+\\+", "category": "runtime" }, { diff --git a/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/CpeUriNormalizer.cs b/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/CpeUriNormalizer.cs new file mode 100644 index 00000000..6ba28546 --- /dev/null +++ b/src/Backend/AutopilotMonitor.Functions/Services/Vulnerability/CpeUriNormalizer.cs @@ -0,0 +1,51 @@ +using System.Text; + +namespace AutopilotMonitor.Functions.Services.Vulnerability +{ + /// + /// Normalizes CPE 2.3 formatted strings into their canonical NVD form. + /// + /// Background: NVD's CPE Dictionary uses the CPE 2.3 "formatted string" form + /// (NIST IR 7695), which requires special characters like '+' to be quoted + /// with a backslash, e.g. "visual_c\+\+". The NVD website often displays the + /// URL-encoded form ("visual_c%2b%2b") in browser address bars, which leads + /// users to copy that into custom mappings — but the API expects the + /// backslash-quoted form. Storing the URL-encoded form causes + /// double-encoding when the value is later passed through + /// Uri.EscapeDataString and breaks both NVD lookups and cache key matching. + /// + /// This helper rewrites URL-encoded '+' (%2B/%2b) and bare '+' into '\+', + /// while leaving already-quoted '\+' alone. Other CPE special characters + /// are intentionally not handled here — '+' is the only one observed in + /// practice — to avoid surprising rewrites of legitimate wildcard chars + /// (e.g. '*' which has semantic meaning as ANY in CPE 2.3). + /// + public static class CpeUriNormalizer + { + public static string Normalize(string? value) + { + if (string.IsNullOrEmpty(value)) return value ?? string.Empty; + + // 1) URL-decode the only encoding we care about: %2B / %2b -> + + // (Done manually to avoid touching other %xx sequences that may + // legitimately appear inside a CPE token.) + var decoded = value!.Replace("%2B", "+").Replace("%2b", "+"); + + // 2) Backslash-quote every bare '+' (i.e. '+' not already preceded + // by a backslash). Walk char-by-char so we don't double-quote. + var sb = new StringBuilder(decoded.Length + 4); + for (int i = 0; i < decoded.Length; i++) + { + char c = decoded[i]; + if (c == '+') + { + bool alreadyQuoted = i > 0 && decoded[i - 1] == '\\'; + if (!alreadyQuoted) sb.Append('\\'); + } + sb.Append(c); + } + + return sb.ToString(); + } + } +}