Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Evidence of a CVE fix in a distro package.
|
||||
/// </summary>
|
||||
public sealed record FixEvidence
|
||||
{
|
||||
/// <summary>Distro identifier (e.g., "debian", "ubuntu", "alpine")</summary>
|
||||
public required string Distro { get; init; }
|
||||
|
||||
/// <summary>Release/codename (e.g., "bookworm", "jammy", "v3.19")</summary>
|
||||
public required string Release { get; init; }
|
||||
|
||||
/// <summary>Source package name</summary>
|
||||
public required string SourcePkg { get; init; }
|
||||
|
||||
/// <summary>CVE identifier (e.g., "CVE-2024-1234")</summary>
|
||||
public required string CveId { get; init; }
|
||||
|
||||
/// <summary>Fix state</summary>
|
||||
public required FixState State { get; init; }
|
||||
|
||||
/// <summary>Version where the fix was applied (if applicable)</summary>
|
||||
public string? FixedVersion { get; init; }
|
||||
|
||||
/// <summary>Method used to detect the fix</summary>
|
||||
public required FixMethod Method { get; init; }
|
||||
|
||||
/// <summary>Confidence score (0.0 - 1.0)</summary>
|
||||
public required decimal Confidence { get; init; }
|
||||
|
||||
/// <summary>Evidence payload for audit trail</summary>
|
||||
public required FixEvidencePayload Evidence { get; init; }
|
||||
|
||||
/// <summary>Corpus snapshot ID (if from snapshot ingestion)</summary>
|
||||
public Guid? SnapshotId { get; init; }
|
||||
|
||||
/// <summary>Timestamp when this evidence was created</summary>
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix state enumeration.
|
||||
/// </summary>
|
||||
public enum FixState
|
||||
{
|
||||
/// <summary>CVE is fixed in this version</summary>
|
||||
Fixed,
|
||||
|
||||
/// <summary>CVE affects this package</summary>
|
||||
Vulnerable,
|
||||
|
||||
/// <summary>CVE does not affect this package</summary>
|
||||
NotAffected,
|
||||
|
||||
/// <summary>Fix won't be applied (e.g., EOL version)</summary>
|
||||
Wontfix,
|
||||
|
||||
/// <summary>Unknown status</summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used to identify the fix.
|
||||
/// </summary>
|
||||
public enum FixMethod
|
||||
{
|
||||
/// <summary>From official security feed (OVAL, DSA, etc.)</summary>
|
||||
SecurityFeed,
|
||||
|
||||
/// <summary>Parsed from Debian/Ubuntu changelog</summary>
|
||||
Changelog,
|
||||
|
||||
/// <summary>Extracted from patch header (DEP-3)</summary>
|
||||
PatchHeader,
|
||||
|
||||
/// <summary>Matched against upstream patch database</summary>
|
||||
UpstreamPatchMatch
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for evidence payloads.
|
||||
/// </summary>
|
||||
public abstract record FixEvidencePayload;
|
||||
|
||||
/// <summary>
|
||||
/// Evidence from changelog parsing.
|
||||
/// </summary>
|
||||
public sealed record ChangelogEvidence : FixEvidencePayload
|
||||
{
|
||||
/// <summary>Path to changelog file</summary>
|
||||
public required string File { get; init; }
|
||||
|
||||
/// <summary>Version from changelog entry</summary>
|
||||
public required string Version { get; init; }
|
||||
|
||||
/// <summary>Excerpt from changelog mentioning CVE</summary>
|
||||
public required string Excerpt { get; init; }
|
||||
|
||||
/// <summary>Line number where CVE was mentioned</summary>
|
||||
public int? LineNumber { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evidence from patch header parsing.
|
||||
/// </summary>
|
||||
public sealed record PatchHeaderEvidence : FixEvidencePayload
|
||||
{
|
||||
/// <summary>Path to patch file</summary>
|
||||
public required string PatchPath { get; init; }
|
||||
|
||||
/// <summary>SHA-256 digest of patch file</summary>
|
||||
public required string PatchSha256 { get; init; }
|
||||
|
||||
/// <summary>Excerpt from patch header</summary>
|
||||
public required string HeaderExcerpt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evidence from official security feed.
|
||||
/// </summary>
|
||||
public sealed record SecurityFeedEvidence : FixEvidencePayload
|
||||
{
|
||||
/// <summary>Feed identifier (e.g., "alpine-secfixes", "debian-oval")</summary>
|
||||
public required string FeedId { get; init; }
|
||||
|
||||
/// <summary>Entry identifier within the feed</summary>
|
||||
public required string EntryId { get; init; }
|
||||
|
||||
/// <summary>Published timestamp from feed</summary>
|
||||
public required DateTimeOffset PublishedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Parses Alpine APKBUILD secfixes section for CVE fix evidence.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// APKBUILD secfixes format:
|
||||
/// # secfixes:
|
||||
/// # 1.2.3-r0:
|
||||
/// # - CVE-2024-1234
|
||||
/// # - CVE-2024-1235
|
||||
/// </remarks>
|
||||
public sealed partial class AlpineSecfixesParser : ISecfixesParser
|
||||
{
|
||||
[GeneratedRegex(@"^#\s*secfixes:\s*$", RegexOptions.Compiled | RegexOptions.Multiline)]
|
||||
private static partial Regex SecfixesPatternRegex();
|
||||
|
||||
[GeneratedRegex(@"^#\s+(\d+\.\d+[^:]*):$", RegexOptions.Compiled)]
|
||||
private static partial Regex VersionPatternRegex();
|
||||
|
||||
[GeneratedRegex(@"^#\s+-\s+(CVE-\d{4}-\d{4,7})$", RegexOptions.Compiled)]
|
||||
private static partial Regex CvePatternRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Parses APKBUILD secfixes section for version-to-CVE mappings.
|
||||
/// </summary>
|
||||
public IEnumerable<FixEvidence> Parse(
|
||||
string apkbuild,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(apkbuild))
|
||||
yield break;
|
||||
|
||||
var lines = apkbuild.Split('\n');
|
||||
var inSecfixes = false;
|
||||
string? currentVersion = null;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (SecfixesPatternRegex().IsMatch(line))
|
||||
{
|
||||
inSecfixes = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inSecfixes)
|
||||
continue;
|
||||
|
||||
// Exit secfixes block on non-comment line
|
||||
if (!line.TrimStart().StartsWith('#'))
|
||||
{
|
||||
inSecfixes = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var versionMatch = VersionPatternRegex().Match(line);
|
||||
if (versionMatch.Success)
|
||||
{
|
||||
currentVersion = versionMatch.Groups[1].Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
var cveMatch = CvePatternRegex().Match(line);
|
||||
if (cveMatch.Success && currentVersion != null)
|
||||
{
|
||||
yield return new FixEvidence
|
||||
{
|
||||
Distro = distro,
|
||||
Release = release,
|
||||
SourcePkg = sourcePkg,
|
||||
CveId = cveMatch.Groups[1].Value,
|
||||
State = FixState.Fixed,
|
||||
FixedVersion = currentVersion,
|
||||
Method = FixMethod.SecurityFeed, // APKBUILD is authoritative
|
||||
Confidence = 0.95m,
|
||||
Evidence = new SecurityFeedEvidence
|
||||
{
|
||||
FeedId = "alpine-secfixes",
|
||||
EntryId = $"{sourcePkg}/{currentVersion}",
|
||||
PublishedAt = DateTimeOffset.UtcNow
|
||||
},
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Parses Debian/Ubuntu changelog files for CVE mentions.
|
||||
/// </summary>
|
||||
public sealed partial class DebianChangelogParser : IChangelogParser
|
||||
{
|
||||
[GeneratedRegex(@"\bCVE-\d{4}-\d{4,7}\b", RegexOptions.Compiled)]
|
||||
private static partial Regex CvePatternRegex();
|
||||
|
||||
[GeneratedRegex(@"^(\S+)\s+\(([^)]+)\)\s+", RegexOptions.Compiled)]
|
||||
private static partial Regex EntryHeaderPatternRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s+--\s+", RegexOptions.Compiled)]
|
||||
private static partial Regex TrailerPatternRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Parses the top entry of a Debian changelog for CVE mentions.
|
||||
/// </summary>
|
||||
public IEnumerable<FixEvidence> ParseTopEntry(
|
||||
string changelog,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(changelog))
|
||||
yield break;
|
||||
|
||||
var lines = changelog.Split('\n');
|
||||
if (lines.Length == 0)
|
||||
yield break;
|
||||
|
||||
// Parse first entry header: "package (version) distribution; urgency"
|
||||
var headerMatch = EntryHeaderPatternRegex().Match(lines[0]);
|
||||
if (!headerMatch.Success)
|
||||
yield break;
|
||||
|
||||
var version = headerMatch.Groups[2].Value;
|
||||
|
||||
// Collect entry lines until trailer (" -- Maintainer <email> Date")
|
||||
var entryLines = new List<string> { lines[0] };
|
||||
foreach (var line in lines.Skip(1))
|
||||
{
|
||||
entryLines.Add(line);
|
||||
if (TrailerPatternRegex().IsMatch(line))
|
||||
break;
|
||||
}
|
||||
|
||||
var entryText = string.Join('\n', entryLines);
|
||||
var cves = CvePatternRegex().Matches(entryText)
|
||||
.Select(m => m.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var cve in cves)
|
||||
{
|
||||
yield return new FixEvidence
|
||||
{
|
||||
Distro = distro,
|
||||
Release = release,
|
||||
SourcePkg = sourcePkg,
|
||||
CveId = cve,
|
||||
State = FixState.Fixed,
|
||||
FixedVersion = version,
|
||||
Method = FixMethod.Changelog,
|
||||
Confidence = 0.80m,
|
||||
Evidence = new ChangelogEvidence
|
||||
{
|
||||
File = "debian/changelog",
|
||||
Version = version,
|
||||
Excerpt = entryText.Length > 2000 ? entryText[..2000] : entryText,
|
||||
LineNumber = null // Could be enhanced to track line number
|
||||
},
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for parsing changelogs for CVE fix evidence.
|
||||
/// </summary>
|
||||
public interface IChangelogParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the top entry of a changelog for CVE mentions.
|
||||
/// </summary>
|
||||
IEnumerable<FixEvidence> ParseTopEntry(
|
||||
string changelog,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for parsing patch files for CVE fix evidence.
|
||||
/// </summary>
|
||||
public interface IPatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses patches for CVE mentions in headers.
|
||||
/// </summary>
|
||||
IEnumerable<FixEvidence> ParsePatches(
|
||||
IEnumerable<(string path, string content, string sha256)> patches,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg,
|
||||
string version);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for parsing Alpine APKBUILD secfixes for CVE mappings.
|
||||
/// </summary>
|
||||
public interface ISecfixesParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses APKBUILD secfixes section for version-to-CVE mappings.
|
||||
/// </summary>
|
||||
IEnumerable<FixEvidence> Parse(
|
||||
string apkbuild,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.BinaryIndex.FixIndex.Models;
|
||||
|
||||
namespace StellaOps.BinaryIndex.FixIndex.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Parses patch headers (DEP-3 format) for CVE mentions.
|
||||
/// </summary>
|
||||
public sealed partial class PatchHeaderParser : IPatchParser
|
||||
{
|
||||
[GeneratedRegex(@"\bCVE-\d{4}-\d{4,7}\b", RegexOptions.Compiled)]
|
||||
private static partial Regex CvePatternRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Parses patches for CVE mentions in headers.
|
||||
/// </summary>
|
||||
public IEnumerable<FixEvidence> ParsePatches(
|
||||
IEnumerable<(string path, string content, string sha256)> patches,
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg,
|
||||
string version)
|
||||
{
|
||||
foreach (var (path, content, sha256) in patches)
|
||||
{
|
||||
// Read first 80 lines as header (typical patch header size)
|
||||
var headerLines = content.Split('\n').Take(80);
|
||||
var header = string.Join('\n', headerLines);
|
||||
|
||||
// Also check filename for CVE (e.g., "CVE-2024-1234.patch")
|
||||
var searchText = header + "\n" + Path.GetFileName(path);
|
||||
var cves = CvePatternRegex().Matches(searchText)
|
||||
.Select(m => m.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var cve in cves)
|
||||
{
|
||||
yield return new FixEvidence
|
||||
{
|
||||
Distro = distro,
|
||||
Release = release,
|
||||
SourcePkg = sourcePkg,
|
||||
CveId = cve,
|
||||
State = FixState.Fixed,
|
||||
FixedVersion = version,
|
||||
Method = FixMethod.PatchHeader,
|
||||
Confidence = 0.87m,
|
||||
Evidence = new PatchHeaderEvidence
|
||||
{
|
||||
PatchPath = path,
|
||||
PatchSha256 = sha256,
|
||||
HeaderExcerpt = header.Length > 1200 ? header[..1200] : header
|
||||
},
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.BinaryIndex.Core\StellaOps.BinaryIndex.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user