namespace StellaOps.Concelier.SourceIntel;
using System.Text.RegularExpressions;
///
/// Parses patch file headers for CVE references (Tier 3).
/// Supports DEP-3 format (Debian) and standard patch headers.
///
public static partial class PatchHeaderParser
{
///
/// Parse patch file for CVE references.
///
public static PatchHeaderParseResult ParsePatchFile(string patchContent, string patchFilePath)
{
var lines = patchContent.Split('\n').Take(50).ToArray(); // Only check first 50 lines (header)
var cveIds = new HashSet();
var description = "";
var bugReferences = new List();
var origin = "";
foreach (var line in lines)
{
// Stop at actual diff content
if (line.StartsWith("---") || line.StartsWith("+++") || line.StartsWith("@@"))
{
break;
}
// DEP-3 Description field
if (line.StartsWith("Description:"))
{
description = line["Description:".Length..].Trim();
}
// DEP-3 Bug references
if (line.StartsWith("Bug:") || line.StartsWith("Bug-Debian:") || line.StartsWith("Bug-Ubuntu:"))
{
var bugRef = line.Split(':')[1].Trim();
bugReferences.Add(bugRef);
}
// DEP-3 Origin
if (line.StartsWith("Origin:"))
{
origin = line["Origin:".Length..].Trim();
}
// Look for CVE mentions in any line
var cveMatches = CvePatternRegex().Matches(line);
foreach (Match match in cveMatches)
{
cveIds.Add(match.Groups[0].Value);
}
}
// Also check filename for CVE pattern
var filenameCves = CvePatternRegex().Matches(patchFilePath);
foreach (Match match in filenameCves)
{
cveIds.Add(match.Groups[0].Value);
}
var confidence = CalculateConfidence(cveIds.Count, description, origin);
return new PatchHeaderParseResult
{
PatchFilePath = patchFilePath,
CveIds = cveIds.ToList(),
Description = description,
BugReferences = bugReferences,
Origin = origin,
Confidence = confidence,
ParsedAt = DateTimeOffset.UtcNow
};
}
///
/// Batch parse multiple patches from debian/patches directory.
///
public static IReadOnlyList ParsePatchDirectory(
string basePath,
IEnumerable patchFiles)
{
var results = new List();
foreach (var patchFile in patchFiles)
{
try
{
var fullPath = Path.Combine(basePath, patchFile);
if (File.Exists(fullPath))
{
var content = File.ReadAllText(fullPath);
var result = ParsePatchFile(content, patchFile);
if (result.CveIds.Count > 0)
{
results.Add(result);
}
}
}
catch
{
// Skip files that can't be read
continue;
}
}
return results;
}
private static double CalculateConfidence(int cveCount, string description, string origin)
{
if (cveCount == 0)
{
return 0.0;
}
// Base confidence for patch header CVE mention
var confidence = 0.80;
// Bonus for multiple CVEs (more explicit)
if (cveCount > 1)
{
confidence += 0.05;
}
// Bonus for detailed description
if (description.Length > 50)
{
confidence += 0.03;
}
// Bonus for upstream origin
if (origin.Contains("upstream", StringComparison.OrdinalIgnoreCase))
{
confidence += 0.02;
}
return Math.Min(confidence, 0.95);
}
[GeneratedRegex(@"CVE-\d{4}-[0-9A-Za-z]{4,}")]
private static partial Regex CvePatternRegex();
}
public sealed record PatchHeaderParseResult
{
public required string PatchFilePath { get; init; }
public required IReadOnlyList CveIds { get; init; }
public required string Description { get; init; }
public required IReadOnlyList BugReferences { get; init; }
public required string Origin { get; init; }
public required double Confidence { get; init; }
public required DateTimeOffset ParsedAt { get; init; }
}