Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.ProofIntegration/ProofAwareVexGenerator.cs
2026-01-04 21:48:13 +02:00

174 lines
6.1 KiB
C#

namespace StellaOps.Scanner.ProofIntegration;
using Microsoft.Extensions.Logging;
using StellaOps.Attestor.ProofChain.Generators;
using StellaOps.Attestor.ProofChain.Models;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.Concelier.ProofService;
/// <summary>
/// Generates VEX verdicts with cryptographic proof references.
/// Integrates Scanner vulnerability detection with proof-driven backport detection.
/// </summary>
public sealed class ProofAwareVexGenerator
{
private readonly ILogger<ProofAwareVexGenerator> _logger;
private readonly BackportProofService _proofService;
private readonly TimeProvider _timeProvider;
public ProofAwareVexGenerator(
ILogger<ProofAwareVexGenerator> logger,
BackportProofService proofService,
TimeProvider? timeProvider = null)
{
_logger = logger;
_proofService = proofService;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Generate VEX verdict with proof for a vulnerability finding.
/// </summary>
/// <param name="finding">Vulnerability finding from scanner</param>
/// <param name="sbomEntryId">SBOM entry ID for the component</param>
/// <param name="policyVersion">Policy version used for decisioning</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>VEX verdict statement with embedded proof reference</returns>
public async Task<VexVerdictWithProof> GenerateVexWithProofAsync(
VulnerabilityFinding finding,
string sbomEntryId,
string policyVersion,
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Generating proof-carrying VEX verdict for {CveId} in {Package}",
finding.CveId, finding.PackagePurl);
// Step 1: Generate cryptographic proof using four-tier detection
var proof = await _proofService.GenerateProofAsync(
finding.CveId,
finding.PackagePurl,
cancellationToken);
if (proof == null)
{
_logger.LogWarning(
"No proof generated for {CveId} in {Package}, using fallback verdict",
finding.CveId, finding.PackagePurl);
// Fallback: Generate VEX without proof
return GenerateFallbackVex(finding, sbomEntryId, policyVersion);
}
_logger.LogInformation(
"Generated proof {ProofId} with confidence {Confidence:P0} for {CveId}",
proof.ProofId, proof.Confidence, finding.CveId);
// Step 2: Generate VEX verdict with proof reference
var reasoningId = GenerateReasoningId(finding, proof);
var (statement, proofPayload) = VexProofIntegrator.GenerateWithProofMetadata(
proof,
sbomEntryId,
policyVersion,
reasoningId);
return new VexVerdictWithProof
{
Statement = statement,
ProofPayload = proofPayload,
Proof = proof,
GeneratedAt = _timeProvider.GetUtcNow()
};
}
/// <summary>
/// Generate VEX verdicts for multiple findings in batch.
/// </summary>
public async Task<IReadOnlyList<VexVerdictWithProof>> GenerateBatchVexWithProofAsync(
IEnumerable<VulnerabilityFinding> findings,
string policyVersion,
Func<VulnerabilityFinding, string> sbomEntryIdResolver,
CancellationToken cancellationToken = default)
{
var tasks = findings.Select(finding =>
{
var sbomEntryId = sbomEntryIdResolver(finding);
return GenerateVexWithProofAsync(finding, sbomEntryId, policyVersion, cancellationToken);
});
var results = await Task.WhenAll(tasks);
return results.ToList();
}
/// <summary>
/// Retrieve existing proof for a CVE + package combination.
/// Useful for audit replay and verification.
/// </summary>
public async Task<ProofBlob?> RetrieveProofAsync(
string cveId,
string packagePurl,
CancellationToken cancellationToken = default)
{
return await _proofService.GenerateProofAsync(cveId, packagePurl, cancellationToken);
}
private VexVerdictWithProof GenerateFallbackVex(
VulnerabilityFinding finding,
string sbomEntryId,
string policyVersion)
{
// Generate basic VEX without proof
// This is used when no evidence is available (e.g., newly disclosed CVE)
var unknownProof = BackportProofGenerator.Unknown(
finding.CveId,
finding.PackagePurl,
"no_evidence_available",
Array.Empty<ProofEvidence>());
var reasoningId = $"reasoning:{finding.CveId}:{finding.PackagePurl}";
var (statement, proofPayload) = VexProofIntegrator.GenerateWithProofMetadata(
unknownProof,
sbomEntryId,
policyVersion,
reasoningId);
return new VexVerdictWithProof
{
Statement = statement,
ProofPayload = proofPayload,
Proof = unknownProof,
GeneratedAt = _timeProvider.GetUtcNow()
};
}
private string GenerateReasoningId(VulnerabilityFinding finding, ProofBlob proof)
{
// Reasoning ID format: reasoning:{cve}:{method}:{snapshot}
return $"reasoning:{finding.CveId}:{proof.Method}:{proof.SnapshotId}";
}
}
/// <summary>
/// Vulnerability finding from scanner.
/// </summary>
public sealed record VulnerabilityFinding
{
public required string CveId { get; init; }
public required string PackagePurl { get; init; }
public required string PackageName { get; init; }
public required string PackageVersion { get; init; }
public required string Severity { get; init; }
}
/// <summary>
/// VEX verdict with associated proof.
/// </summary>
public sealed record VexVerdictWithProof
{
public required VexVerdictStatement Statement { get; init; }
public required VexVerdictProofPayload ProofPayload { get; init; }
public required ProofBlob Proof { get; init; }
public required DateTimeOffset GeneratedAt { get; init; }
}