save checkpoint
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Helpers;
|
||||
|
||||
public static partial class ChangelogBugReferenceExtractor
|
||||
{
|
||||
public static ChangelogBugReferenceExtractionResult Extract(params string?[] changelogInputs)
|
||||
{
|
||||
if (changelogInputs is null || changelogInputs.Length == 0)
|
||||
{
|
||||
return ChangelogBugReferenceExtractionResult.Empty;
|
||||
}
|
||||
|
||||
var bugReferences = new SortedSet<string>(StringComparer.Ordinal);
|
||||
var bugToCves = new SortedDictionary<string, SortedSet<string>>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var input in changelogInputs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in SplitEntries(input))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cves = ExtractCves(entry);
|
||||
var bugsInEntry = ExtractBugs(entry);
|
||||
|
||||
foreach (var bug in bugsInEntry)
|
||||
{
|
||||
bugReferences.Add(bug);
|
||||
}
|
||||
|
||||
if (cves.Count == 0 || bugsInEntry.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var bug in bugsInEntry)
|
||||
{
|
||||
if (!bugToCves.TryGetValue(bug, out var mapped))
|
||||
{
|
||||
mapped = new SortedSet<string>(StringComparer.Ordinal);
|
||||
bugToCves[bug] = mapped;
|
||||
}
|
||||
|
||||
mapped.UnionWith(cves);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bugReferences.Count == 0)
|
||||
{
|
||||
return ChangelogBugReferenceExtractionResult.Empty;
|
||||
}
|
||||
|
||||
var immutableMap = new ReadOnlyDictionary<string, IReadOnlyList<string>>(
|
||||
bugToCves.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => (IReadOnlyList<string>)new ReadOnlyCollection<string>(pair.Value.ToArray()),
|
||||
StringComparer.Ordinal));
|
||||
|
||||
return new ChangelogBugReferenceExtractionResult(
|
||||
new ReadOnlyCollection<string>(bugReferences.ToArray()),
|
||||
immutableMap);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> SplitEntries(string input)
|
||||
{
|
||||
var entries = new List<string>();
|
||||
foreach (var paragraph in EntrySeparatorRegex().Split(input))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(paragraph))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var line in paragraph.Split('\n'))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.Add(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return entries.Count == 0
|
||||
? new[] { input.Trim() }
|
||||
: entries;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ExtractCves(string entry)
|
||||
{
|
||||
var cves = new SortedSet<string>(StringComparer.Ordinal);
|
||||
foreach (Match match in CveRegex().Matches(entry))
|
||||
{
|
||||
cves.Add(match.Value.ToUpperInvariant());
|
||||
}
|
||||
|
||||
return cves.ToArray();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ExtractBugs(string entry)
|
||||
{
|
||||
var bugs = new SortedSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (Match closesMatch in DebianClosesRegex().Matches(entry))
|
||||
{
|
||||
foreach (Match idMatch in HashBugIdRegex().Matches(closesMatch.Value))
|
||||
{
|
||||
bugs.Add($"debian:#{idMatch.Groups["id"].Value}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Match rhbz in RhbzRegex().Matches(entry))
|
||||
{
|
||||
bugs.Add($"rhbz:#{rhbz.Groups["id"].Value}");
|
||||
}
|
||||
|
||||
foreach (Match lp in LaunchpadShortRegex().Matches(entry))
|
||||
{
|
||||
bugs.Add($"launchpad:#{lp.Groups["id"].Value}");
|
||||
}
|
||||
|
||||
foreach (Match lp in LaunchpadLongRegex().Matches(entry))
|
||||
{
|
||||
bugs.Add($"launchpad:#{lp.Groups["id"].Value}");
|
||||
}
|
||||
|
||||
return bugs.ToArray();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(?:\r?\n){2,}", RegexOptions.Compiled)]
|
||||
private static partial Regex EntrySeparatorRegex();
|
||||
|
||||
[GeneratedRegex(@"CVE-\d{4}-\d{4,7}", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex CveRegex();
|
||||
|
||||
[GeneratedRegex(@"\bCloses\s*:\s*(?:#\d+[,\s]*)+", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex DebianClosesRegex();
|
||||
|
||||
[GeneratedRegex(@"#(?<id>\d+)", RegexOptions.Compiled)]
|
||||
private static partial Regex HashBugIdRegex();
|
||||
|
||||
[GeneratedRegex(@"\bRHBZ\s*#\s*(?<id>\d+)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex RhbzRegex();
|
||||
|
||||
[GeneratedRegex(@"\bLP\s*:\s*#\s*(?<id>\d+)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex LaunchpadShortRegex();
|
||||
|
||||
[GeneratedRegex(@"\bLaunchpad(?:\s+bug)?\s*#\s*(?<id>\d+)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
|
||||
private static partial Regex LaunchpadLongRegex();
|
||||
}
|
||||
|
||||
public sealed record ChangelogBugReferenceExtractionResult(
|
||||
IReadOnlyList<string> BugReferences,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<string>> BugToCves)
|
||||
{
|
||||
public static ChangelogBugReferenceExtractionResult Empty { get; } = new(
|
||||
Array.Empty<string>(),
|
||||
new ReadOnlyDictionary<string, IReadOnlyList<string>>(
|
||||
new Dictionary<string, IReadOnlyList<string>>(0, StringComparer.Ordinal)));
|
||||
|
||||
public string ToBugReferencesMetadataValue()
|
||||
=> string.Join(",", BugReferences);
|
||||
|
||||
public string ToBugToCvesMetadataValue()
|
||||
=> string.Join(
|
||||
";",
|
||||
BugToCves.Select(static pair => $"{pair.Key}=>{string.Join('|', pair.Value)}"));
|
||||
}
|
||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-010 | DONE | Implemented deterministic changelog bug-id to CVE mapping (`Closes`, `RHBZ`, `LP`) for OS analyzers with Tier 0/1/2 evidence in run-001. |
|
||||
| REMED-06-SOLID | DOING | SOLID review for OS analyzer files (Tier 0 remediation batch) in progress. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
Reference in New Issue
Block a user