save progress
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
namespace StellaOps.Concelier.SourceIntel;
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/// <summary>
|
||||
@@ -8,7 +9,7 @@ using System.Text.RegularExpressions;
|
||||
public static partial class ChangelogParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse Debian changelog for CVE mentions.
|
||||
/// Parse Debian changelog for CVE mentions and bug references.
|
||||
/// </summary>
|
||||
public static ChangelogParseResult ParseDebianChangelog(string changelogContent)
|
||||
{
|
||||
@@ -19,6 +20,7 @@ public static partial class ChangelogParser
|
||||
string? currentVersion = null;
|
||||
DateTimeOffset? currentDate = null;
|
||||
var currentCves = new List<string>();
|
||||
var currentBugs = new List<BugReference>();
|
||||
var currentDescription = new List<string>();
|
||||
|
||||
foreach (var line in lines)
|
||||
@@ -28,22 +30,24 @@ public static partial class ChangelogParser
|
||||
if (headerMatch.Success)
|
||||
{
|
||||
// Save previous entry
|
||||
if (currentPackage != null && currentVersion != null && currentCves.Count > 0)
|
||||
if (currentPackage != null && currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = currentPackage,
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = string.Join(" ", currentDescription),
|
||||
Date = currentDate ?? DateTimeOffset.UtcNow,
|
||||
Confidence = 0.80
|
||||
Confidence = currentCves.Count > 0 ? 0.80 : 0.75 // Bug-only entries have lower confidence
|
||||
});
|
||||
}
|
||||
|
||||
currentPackage = headerMatch.Groups[1].Value;
|
||||
currentVersion = headerMatch.Groups[2].Value;
|
||||
currentCves.Clear();
|
||||
currentBugs.Clear();
|
||||
currentDescription.Clear();
|
||||
currentDate = null;
|
||||
continue;
|
||||
@@ -68,6 +72,16 @@ public static partial class ChangelogParser
|
||||
}
|
||||
}
|
||||
|
||||
// Content lines: look for bug references
|
||||
var bugRefs = ExtractBugReferences(line);
|
||||
foreach (var bug in bugRefs)
|
||||
{
|
||||
if (!currentBugs.Any(b => b.Tracker == bug.Tracker && b.BugId == bug.BugId))
|
||||
{
|
||||
currentBugs.Add(bug);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith(" --"))
|
||||
{
|
||||
currentDescription.Add(line.Trim());
|
||||
@@ -75,16 +89,17 @@ public static partial class ChangelogParser
|
||||
}
|
||||
|
||||
// Save last entry
|
||||
if (currentPackage != null && currentVersion != null && currentCves.Count > 0)
|
||||
if (currentPackage != null && currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = currentPackage,
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = string.Join(" ", currentDescription),
|
||||
Date = currentDate ?? DateTimeOffset.UtcNow,
|
||||
Confidence = 0.80
|
||||
Confidence = currentCves.Count > 0 ? 0.80 : 0.75
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,7 +111,7 @@ public static partial class ChangelogParser
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse RPM changelog for CVE mentions.
|
||||
/// Parse RPM changelog for CVE mentions and bug references.
|
||||
/// </summary>
|
||||
public static ChangelogParseResult ParseRpmChangelog(string changelogContent)
|
||||
{
|
||||
@@ -106,6 +121,7 @@ public static partial class ChangelogParser
|
||||
string? currentVersion = null;
|
||||
DateTimeOffset? currentDate = null;
|
||||
var currentCves = new List<string>();
|
||||
var currentBugs = new List<BugReference>();
|
||||
var currentDescription = new List<string>();
|
||||
|
||||
foreach (var line in lines)
|
||||
@@ -115,22 +131,24 @@ public static partial class ChangelogParser
|
||||
if (headerMatch.Success)
|
||||
{
|
||||
// Save previous entry
|
||||
if (currentVersion != null && currentCves.Count > 0)
|
||||
if (currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = "rpm-package", // Extracted from spec file name
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = string.Join(" ", currentDescription),
|
||||
Date = currentDate ?? DateTimeOffset.UtcNow,
|
||||
Confidence = 0.80
|
||||
Confidence = currentCves.Count > 0 ? 0.80 : 0.75
|
||||
});
|
||||
}
|
||||
|
||||
currentDate = ParseRpmDate(headerMatch.Groups[1].Value);
|
||||
currentVersion = headerMatch.Groups[2].Value;
|
||||
currentCves.Clear();
|
||||
currentBugs.Clear();
|
||||
currentDescription.Clear();
|
||||
continue;
|
||||
}
|
||||
@@ -146,6 +164,16 @@ public static partial class ChangelogParser
|
||||
}
|
||||
}
|
||||
|
||||
// Content lines: look for bug references
|
||||
var bugRefs = ExtractBugReferences(line);
|
||||
foreach (var bug in bugRefs)
|
||||
{
|
||||
if (!currentBugs.Any(b => b.Tracker == bug.Tracker && b.BugId == bug.BugId))
|
||||
{
|
||||
currentBugs.Add(bug);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith("*"))
|
||||
{
|
||||
currentDescription.Add(line.Trim());
|
||||
@@ -153,16 +181,17 @@ public static partial class ChangelogParser
|
||||
}
|
||||
|
||||
// Save last entry
|
||||
if (currentVersion != null && currentCves.Count > 0)
|
||||
if (currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = "rpm-package",
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = string.Join(" ", currentDescription),
|
||||
Date = currentDate ?? DateTimeOffset.UtcNow,
|
||||
Confidence = 0.80
|
||||
Confidence = currentCves.Count > 0 ? 0.80 : 0.75
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,6 +204,8 @@ public static partial class ChangelogParser
|
||||
|
||||
/// <summary>
|
||||
/// Parse Alpine APKBUILD secfixes for CVE mentions.
|
||||
/// Alpine secfixes typically don't contain bug tracker references, but we include
|
||||
/// the functionality for consistency.
|
||||
/// </summary>
|
||||
public static ChangelogParseResult ParseAlpineSecfixes(string secfixesContent)
|
||||
{
|
||||
@@ -183,6 +214,7 @@ public static partial class ChangelogParser
|
||||
|
||||
string? currentVersion = null;
|
||||
var currentCves = new List<string>();
|
||||
var currentBugs = new List<BugReference>();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
@@ -191,13 +223,14 @@ public static partial class ChangelogParser
|
||||
if (versionMatch.Success)
|
||||
{
|
||||
// Save previous entry
|
||||
if (currentVersion != null && currentCves.Count > 0)
|
||||
if (currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = "alpine-package",
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = $"Security fixes for {string.Join(", ", currentCves)}",
|
||||
Date = DateTimeOffset.UtcNow,
|
||||
Confidence = 0.85 // Alpine secfixes are explicit
|
||||
@@ -206,6 +239,7 @@ public static partial class ChangelogParser
|
||||
|
||||
currentVersion = versionMatch.Groups[1].Value;
|
||||
currentCves.Clear();
|
||||
currentBugs.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -219,16 +253,27 @@ public static partial class ChangelogParser
|
||||
currentCves.Add(cveId);
|
||||
}
|
||||
}
|
||||
|
||||
// Bug references (rare in Alpine secfixes, but possible)
|
||||
var bugRefs = ExtractBugReferences(line);
|
||||
foreach (var bug in bugRefs)
|
||||
{
|
||||
if (!currentBugs.Any(b => b.Tracker == bug.Tracker && b.BugId == bug.BugId))
|
||||
{
|
||||
currentBugs.Add(bug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save last entry
|
||||
if (currentVersion != null && currentCves.Count > 0)
|
||||
if (currentVersion != null && (currentCves.Count > 0 || currentBugs.Count > 0))
|
||||
{
|
||||
entries.Add(new ChangelogEntry
|
||||
{
|
||||
PackageName = "alpine-package",
|
||||
Version = currentVersion,
|
||||
CveIds = currentCves.ToList(),
|
||||
BugReferences = currentBugs.ToList(),
|
||||
Description = $"Security fixes for {string.Join(", ", currentCves)}",
|
||||
Date = DateTimeOffset.UtcNow,
|
||||
Confidence = 0.85
|
||||
@@ -276,6 +321,120 @@ public static partial class ChangelogParser
|
||||
|
||||
[GeneratedRegex(@"CVE-\d{4}-[0-9A-Za-z]{4,}")]
|
||||
private static partial Regex CvePatternRegex();
|
||||
|
||||
// Bug tracker patterns for BP-401, BP-402, BP-403
|
||||
|
||||
/// <summary>
|
||||
/// Debian BTS pattern: matches the "Closes:" or "Fixes:" prefix to identify Debian bug sections.
|
||||
/// The actual bug numbers are extracted separately using DebianBugNumberRegex.
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"(?:Closes|Fixes):\s*(.+?)(?=\s*(?:\(|$|,\s*(?:Closes|Fixes):))", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex DebianBugSectionRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Extract individual bug numbers from a Debian bug section (after "Closes:" or "Fixes:").
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"#?(\d{4,})", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex DebianBugNumberRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Red Hat Bugzilla pattern: "RHBZ#123456", "rhbz#123456", "bz#123456", "Bug 123456"
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"(?:RHBZ|rhbz|bz|Bug|BZ)[\s#:]+(\d{6,8})", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex RedHatBugRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Launchpad pattern: "LP: #123456" or "LP #123456"
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"LP[\s:#]+(\d+)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex LaunchpadBugRegex();
|
||||
|
||||
/// <summary>
|
||||
/// GitHub pattern: "Fixes #123", "GH-123", "#123" in commit context
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"(?:Fixes|Closes|Resolves)?\s*(?:GH-|#)(\d+)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex GitHubBugRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Extract all bug references from a changelog line.
|
||||
/// </summary>
|
||||
public static ImmutableArray<BugReference> ExtractBugReferences(string line)
|
||||
{
|
||||
var bugs = ImmutableArray.CreateBuilder<BugReference>();
|
||||
|
||||
// Debian BTS - find "Closes:" or "Fixes:" sections and extract all numbers
|
||||
if (line.Contains("Closes:", StringComparison.OrdinalIgnoreCase) ||
|
||||
line.Contains("Fixes:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Look for all bug numbers after Closes: or Fixes:
|
||||
var debianSection = DebianBugSectionRegex().Match(line);
|
||||
if (debianSection.Success)
|
||||
{
|
||||
var section = debianSection.Groups[1].Value;
|
||||
foreach (Match numMatch in DebianBugNumberRegex().Matches(section))
|
||||
{
|
||||
var bugId = numMatch.Groups[1].Value;
|
||||
if (!bugs.Any(b => b.Tracker == BugTracker.Debian && b.BugId == bugId))
|
||||
{
|
||||
bugs.Add(new BugReference
|
||||
{
|
||||
Tracker = BugTracker.Debian,
|
||||
BugId = bugId,
|
||||
RawReference = debianSection.Value.Trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: just find any bug number patterns in the line after Closes: or Fixes:
|
||||
var keyword = line.Contains("Closes:", StringComparison.OrdinalIgnoreCase) ? "Closes:" : "Fixes:";
|
||||
var idx = line.IndexOf(keyword, StringComparison.OrdinalIgnoreCase);
|
||||
if (idx >= 0)
|
||||
{
|
||||
var start = idx + keyword.Length;
|
||||
var afterKeyword = start <= line.Length ? line[start..] : string.Empty;
|
||||
foreach (Match numMatch in DebianBugNumberRegex().Matches(afterKeyword))
|
||||
{
|
||||
var bugId = numMatch.Groups[1].Value;
|
||||
if (!bugs.Any(b => b.Tracker == BugTracker.Debian && b.BugId == bugId))
|
||||
{
|
||||
bugs.Add(new BugReference
|
||||
{
|
||||
Tracker = BugTracker.Debian,
|
||||
BugId = bugId,
|
||||
RawReference = $"Closes: #{bugId}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Red Hat Bugzilla
|
||||
foreach (Match match in RedHatBugRegex().Matches(line))
|
||||
{
|
||||
bugs.Add(new BugReference
|
||||
{
|
||||
Tracker = BugTracker.RedHat,
|
||||
BugId = match.Groups[1].Value,
|
||||
RawReference = match.Value
|
||||
});
|
||||
}
|
||||
|
||||
// Launchpad
|
||||
foreach (Match match in LaunchpadBugRegex().Matches(line))
|
||||
{
|
||||
bugs.Add(new BugReference
|
||||
{
|
||||
Tracker = BugTracker.Launchpad,
|
||||
BugId = match.Groups[1].Value,
|
||||
RawReference = match.Value
|
||||
});
|
||||
}
|
||||
|
||||
return bugs.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ChangelogParseResult
|
||||
@@ -289,7 +448,65 @@ public sealed record ChangelogEntry
|
||||
public required string PackageName { get; init; }
|
||||
public required string Version { get; init; }
|
||||
public required IReadOnlyList<string> CveIds { get; init; }
|
||||
public required IReadOnlyList<BugReference> BugReferences { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required DateTimeOffset Date { get; init; }
|
||||
public required double Confidence { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a bug tracker reference extracted from a changelog.
|
||||
/// </summary>
|
||||
public sealed record BugReference
|
||||
{
|
||||
/// <summary>
|
||||
/// The bug tracker system.
|
||||
/// </summary>
|
||||
public required BugTracker Tracker { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The bug ID within that tracker.
|
||||
/// </summary>
|
||||
public required string BugId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The full reference string as found in the changelog.
|
||||
/// </summary>
|
||||
public required string RawReference { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported bug tracker systems for CVE mapping.
|
||||
/// </summary>
|
||||
public enum BugTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Debian BTS - "Closes: #123456" or "(Closes: #123)"
|
||||
/// </summary>
|
||||
Debian,
|
||||
|
||||
/// <summary>
|
||||
/// Red Hat Bugzilla - "RHBZ#123456", "rhbz#123456", "bz#123456"
|
||||
/// </summary>
|
||||
RedHat,
|
||||
|
||||
/// <summary>
|
||||
/// Launchpad - "LP: #123456"
|
||||
/// </summary>
|
||||
Launchpad,
|
||||
|
||||
/// <summary>
|
||||
/// GitHub Issues - "Fixes #123", "GH-123"
|
||||
/// </summary>
|
||||
GitHub,
|
||||
|
||||
/// <summary>
|
||||
/// GitLab Issues - "gitlab#123"
|
||||
/// </summary>
|
||||
GitLab,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown tracker type.
|
||||
/// </summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user