// SPDX-License-Identifier: AGPL-3.0-or-later // Sprint: SPRINT_20260102_001_BE // Task: DS-041 - VEX evidence emission for backport detection using System.Collections.Immutable; using System.Security.Cryptography; using System.Text; using StellaOps.Scanner.Evidence.Models; namespace StellaOps.Scanner.Evidence; /// /// Emits VEX candidates based on delta signature analysis results. /// When a binary is confirmed to have patched code via signature matching, /// this emitter generates a not_affected VEX candidate with full evidence trail. /// public sealed class DeltaSigVexEmitter { private readonly DeltaSigVexEmitterOptions _options; private readonly TimeProvider _timeProvider; /// /// Initializes a new instance of the class. /// public DeltaSigVexEmitter( DeltaSigVexEmitterOptions? options = null, TimeProvider? timeProvider = null) { _options = options ?? DeltaSigVexEmitterOptions.Default; _timeProvider = timeProvider ?? TimeProvider.System; } /// /// Evaluates delta signature evidence and emits VEX candidates for patched binaries. /// public DeltaSigVexEmissionResult EmitCandidates(DeltaSigVexEmissionContext context) { ArgumentNullException.ThrowIfNull(context); var candidates = new List(); foreach (var evidence in context.EvidenceItems) { // Only emit candidates for confirmed patched binaries if (evidence.Result != DeltaSigResult.Patched) continue; // Must meet minimum confidence threshold if (evidence.Confidence < _options.MinConfidence) continue; var candidate = CreateVexCandidate(evidence, context); candidates.Add(candidate); if (candidates.Count >= _options.MaxCandidatesPerBatch) break; } return new DeltaSigVexEmissionResult( ImageDigest: context.ImageDigest, CandidatesEmitted: candidates.Count, Candidates: [.. candidates], GeneratedAt: _timeProvider.GetUtcNow()); } /// /// Creates a VEX candidate from delta signature evidence. /// private DeltaSigVexCandidate CreateVexCandidate( DeltaSignatureEvidence evidence, DeltaSigVexEmissionContext context) { var candidateId = GenerateCandidateId(evidence, context); // Build evidence links var evidenceLinks = new List { new( Type: "delta_signature_analysis", Uri: $"deltasig://{context.ImageDigest}/{evidence.BinaryId}", Description: evidence.Summary) }; // Add symbol-level evidence links foreach (var match in evidence.SymbolMatches) { if (match.State == SignatureState.Patched) { var hashPreview = match.HashHex.Length > 16 ? $"{match.HashHex[..16]}..." : match.HashHex; evidenceLinks.Add(new DeltaSigEvidenceLink( Type: "patched_symbol", Uri: $"symbol://{match.SymbolName}", Description: match.ExactMatch ? $"Exact hash match: {hashPreview}" : $"Partial match: {match.ChunksMatched}/{match.ChunksTotal} chunks")); } } // Add attestation link if available if (evidence.AttestationUri is not null) { evidenceLinks.Add(new DeltaSigEvidenceLink( Type: "dsse_attestation", Uri: evidence.AttestationUri, Description: "Signed attestation envelope")); } return new DeltaSigVexCandidate( CandidateId: candidateId, CveIds: evidence.CveIds, PackagePurl: evidence.PackagePurl, BinaryId: evidence.BinaryId, SuggestedStatus: DeltaSigVexStatus.NotAffected, Justification: DeltaSigVexJustification.VulnerableCodeNotPresent, Rationale: GenerateRationale(evidence), Confidence: evidence.Confidence, EvidenceLinks: [.. evidenceLinks], DeltaSignatureEvidence: evidence, ImageDigest: context.ImageDigest, GeneratedAt: _timeProvider.GetUtcNow(), ExpiresAt: _timeProvider.GetUtcNow().Add(_options.CandidateTtl), RequiresReview: evidence.Confidence < _options.AutoApprovalThreshold); } /// /// Generates a human-readable rationale for the VEX determination. /// private static string GenerateRationale(DeltaSignatureEvidence evidence) { var sb = new StringBuilder(); sb.Append("Binary delta signature analysis confirms the security fix is applied. "); var patchedCount = evidence.SymbolMatches.Count(m => m.State == SignatureState.Patched); var exactCount = evidence.SymbolMatches.Count(m => m.State == SignatureState.Patched && m.ExactMatch); if (exactCount > 0) { sb.Append($"{exactCount} symbol(s) matched patched signatures exactly. "); } if (patchedCount > exactCount) { sb.Append($"{patchedCount - exactCount} symbol(s) matched via chunk analysis. "); } sb.Append($"Confidence: {evidence.Confidence:P0}. "); sb.Append($"Recipe: {evidence.NormalizationRecipe.RecipeId} v{evidence.NormalizationRecipe.Version}."); return sb.ToString(); } /// /// Generates a deterministic candidate ID. /// private string GenerateCandidateId( DeltaSignatureEvidence evidence, DeltaSigVexEmissionContext context) { var input = $"{context.ImageDigest}:{evidence.BinaryId}:{string.Join(",", evidence.CveIds)}:{evidence.GeneratedAt.Ticks}"; var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input)); return $"vexds-{Convert.ToHexString(hash).ToLowerInvariant()[..16]}"; } } /// /// Options for delta signature VEX emission. /// public sealed record DeltaSigVexEmitterOptions { /// /// Minimum confidence required to emit a VEX candidate. /// public decimal MinConfidence { get; init; } = 0.75m; /// /// Confidence threshold for auto-approval (no human review required). /// public decimal AutoApprovalThreshold { get; init; } = 0.95m; /// /// Maximum candidates per emission batch. /// public int MaxCandidatesPerBatch { get; init; } = 100; /// /// Time-to-live for candidates before they expire. /// public TimeSpan CandidateTtl { get; init; } = TimeSpan.FromDays(30); /// /// Default options. /// public static DeltaSigVexEmitterOptions Default { get; } = new(); } /// /// Context for delta signature VEX emission. /// public sealed record DeltaSigVexEmissionContext( string ImageDigest, IReadOnlyList EvidenceItems); /// /// Result of delta signature VEX emission. /// public sealed record DeltaSigVexEmissionResult( string ImageDigest, int CandidatesEmitted, ImmutableArray Candidates, DateTimeOffset GeneratedAt); /// /// A VEX candidate generated from delta signature analysis. /// public sealed record DeltaSigVexCandidate( string CandidateId, ImmutableArray CveIds, string PackagePurl, string BinaryId, DeltaSigVexStatus SuggestedStatus, DeltaSigVexJustification Justification, string Rationale, decimal Confidence, ImmutableArray EvidenceLinks, DeltaSignatureEvidence DeltaSignatureEvidence, string ImageDigest, DateTimeOffset GeneratedAt, DateTimeOffset ExpiresAt, bool RequiresReview); /// /// VEX status for delta signature candidates. /// public enum DeltaSigVexStatus { /// /// Not affected - patched code is present. /// NotAffected, /// /// Affected - vulnerable code is present. /// Affected, /// /// Under investigation - analysis was inconclusive. /// UnderInvestigation } /// /// VEX justification for delta signature candidates. /// public enum DeltaSigVexJustification { /// /// Vulnerable code is not present (patched binary). /// VulnerableCodeNotPresent, /// /// Component was rebuilt with fix. /// ComponentRebuiltWithFix } /// /// Evidence link for delta signature VEX candidates. /// public sealed record DeltaSigEvidenceLink( string Type, string Uri, string? Description = null);