Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -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; }
}

View File

@@ -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
};
}
}
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
};
}
}
}
}

View File

@@ -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>