// ----------------------------------------------------------------------------- // DeltaSigVexBridge.cs // Sprint: SPRINT_20260119_004_BinaryIndex_deltasig_extensions // Task: DSIG-005 - VEX Evidence Integration // Description: Bridges DeltaSig v2 predicates with VEX statement generation // ----------------------------------------------------------------------------- using System.Text.Json; using Microsoft.Extensions.Logging; using StellaOps.BinaryIndex.DeltaSig.Attestation; namespace StellaOps.BinaryIndex.DeltaSig.VexIntegration; /// /// Bridges DeltaSig v2 predicates with VEX observations. /// public interface IDeltaSigVexBridge { /// /// Generates a VEX observation from a DeltaSig v2 predicate. /// /// The v2 predicate. /// VEX generation context. /// Cancellation token. /// VEX observation. Task GenerateFromPredicateAsync( DeltaSigPredicateV2 predicate, DeltaSigVexContext context, CancellationToken ct = default); /// /// Converts a v2 predicate verdict to a VEX statement status. /// /// The DeltaSig verdict. /// VEX statement status. VexStatus MapVerdictToStatus(string verdict); /// /// Extracts evidence blocks from a v2 predicate. /// /// The v2 predicate. /// Evidence blocks for VEX attachment. IReadOnlyList ExtractEvidence(DeltaSigPredicateV2 predicate); } /// /// Implementation of DeltaSig-VEX bridge. /// public sealed class DeltaSigVexBridge : IDeltaSigVexBridge { private readonly ILogger _logger; private readonly TimeProvider _timeProvider; /// /// Creates a new bridge instance. /// public DeltaSigVexBridge( ILogger logger, TimeProvider? timeProvider = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _timeProvider = timeProvider ?? TimeProvider.System; } /// public Task GenerateFromPredicateAsync( DeltaSigPredicateV2 predicate, DeltaSigVexContext context, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(predicate); ArgumentNullException.ThrowIfNull(context); var status = MapVerdictToStatus(predicate.Verdict); var evidence = ExtractEvidence(predicate); var observationId = GenerateObservationId(context, predicate); var observation = new VexObservation { ObservationId = observationId, TenantId = context.TenantId, ProviderId = "stellaops.deltasig", StreamId = "deltasig_resolution", Purl = predicate.Subject.Purl, CveId = predicate.CveIds?.FirstOrDefault() ?? string.Empty, Status = status, Justification = MapVerdictToJustification(predicate.Verdict), Impact = null, ActionStatement = BuildActionStatement(predicate, context), ObservedAt = _timeProvider.GetUtcNow(), Provenance = new VexProvenance { Source = "deltasig-v2", Method = "binary-diff-analysis", Confidence = predicate.Confidence, ToolVersion = GetToolVersion(), SourceUri = context.SourceUri }, Evidence = evidence, Supersedes = context.SupersedesObservationId, Metadata = BuildMetadata(predicate, context) }; _logger.LogInformation( "Generated VEX observation {Id} from DeltaSig predicate: {Status} for {Purl}", observationId, status, predicate.Subject.Purl); return Task.FromResult(observation); } /// public VexStatus MapVerdictToStatus(string verdict) { return verdict switch { DeltaSigVerdicts.Patched => VexStatus.Fixed, DeltaSigVerdicts.Vulnerable => VexStatus.Affected, DeltaSigVerdicts.PartiallyPatched => VexStatus.UnderInvestigation, DeltaSigVerdicts.Inconclusive => VexStatus.UnderInvestigation, DeltaSigVerdicts.Unknown => VexStatus.NotAffected, // Assume not affected if unknown _ => VexStatus.UnderInvestigation }; } /// public IReadOnlyList ExtractEvidence(DeltaSigPredicateV2 predicate) { var blocks = new List(); // Summary evidence if (predicate.Summary != null) { blocks.Add(new VexEvidenceBlock { Type = "deltasig-summary", Label = "DeltaSig Analysis Summary", Content = JsonSerializer.Serialize(new { predicate.Summary.TotalFunctions, predicate.Summary.VulnerableFunctions, predicate.Summary.PatchedFunctions, predicate.Summary.FunctionsWithProvenance, predicate.Summary.FunctionsWithIrDiff, predicate.Summary.AvgMatchScore }), ContentType = "application/json" }); } // Function-level evidence for high-confidence matches var highConfidenceMatches = predicate.FunctionMatches .Where(f => f.MatchScore >= 0.9 && f.SymbolProvenance != null) .Take(10) // Limit to avoid bloat .ToList(); if (highConfidenceMatches.Count > 0) { blocks.Add(new VexEvidenceBlock { Type = "deltasig-function-matches", Label = "High-Confidence Function Matches", Content = JsonSerializer.Serialize(highConfidenceMatches.Select(f => new { f.Name, f.MatchScore, f.MatchMethod, f.MatchState, ProvenanceSource = f.SymbolProvenance?.SourceId, HasIrDiff = f.IrDiff != null })), ContentType = "application/json" }); } // Predicate reference blocks.Add(new VexEvidenceBlock { Type = "deltasig-predicate-ref", Label = "DeltaSig Predicate Reference", Content = JsonSerializer.Serialize(new { PredicateType = DeltaSigPredicateV2.PredicateType, predicate.Verdict, predicate.Confidence, predicate.ComputedAt, CveIds = predicate.CveIds }), ContentType = "application/json" }); return blocks; } private static string GenerateObservationId(DeltaSigVexContext context, DeltaSigPredicateV2 predicate) { // Generate deterministic observation ID using UUID5 var input = $"{context.TenantId}:{predicate.Subject.Purl}:{predicate.CveIds?.FirstOrDefault()}:{predicate.ComputedAt:O}"; return $"obs:deltasig:{ComputeHash(input)}"; } private static string? MapVerdictToJustification(string verdict) { return verdict switch { DeltaSigVerdicts.Patched => "vulnerable_code_not_present", DeltaSigVerdicts.PartiallyPatched => "inline_mitigations_already_exist", _ => null }; } private static string? BuildActionStatement(DeltaSigPredicateV2 predicate, DeltaSigVexContext context) { return predicate.Verdict switch { DeltaSigVerdicts.Patched => $"Binary analysis confirms {predicate.Summary?.PatchedFunctions ?? 0} vulnerable functions have been patched.", DeltaSigVerdicts.Vulnerable => $"Binary analysis detected {predicate.Summary?.VulnerableFunctions ?? 0} unpatched vulnerable functions. Upgrade recommended.", DeltaSigVerdicts.PartiallyPatched => "Some vulnerable functions remain unpatched. Review required.", _ => null }; } private static IReadOnlyDictionary? BuildMetadata( DeltaSigPredicateV2 predicate, DeltaSigVexContext context) { var metadata = new Dictionary { ["predicateType"] = DeltaSigPredicateV2.PredicateType, ["verdict"] = predicate.Verdict, ["confidence"] = predicate.Confidence.ToString("F2"), ["computedAt"] = predicate.ComputedAt.ToString("O") }; if (predicate.Tooling != null) { metadata["lifter"] = predicate.Tooling.Lifter; metadata["matchAlgorithm"] = predicate.Tooling.MatchAlgorithm ?? "unknown"; } if (context.ScanId != null) { metadata["scanId"] = context.ScanId; } return metadata; } private static string GetToolVersion() { var version = typeof(DeltaSigVexBridge).Assembly.GetName().Version; return version?.ToString() ?? "0.0.0"; } private static string ComputeHash(string input) { var bytes = System.Text.Encoding.UTF8.GetBytes(input); var hash = System.Security.Cryptography.SHA256.HashData(bytes); return Convert.ToHexString(hash)[..16].ToLowerInvariant(); } } /// /// Context for DeltaSig VEX generation. /// public sealed record DeltaSigVexContext { /// /// Tenant identifier. /// public required string TenantId { get; init; } /// /// Optional scan identifier. /// public string? ScanId { get; init; } /// /// Optional source URI for the predicate. /// public string? SourceUri { get; init; } /// /// Optional observation ID this supersedes. /// public string? SupersedesObservationId { get; init; } } /// /// VEX status enum (mirrors Excititor.Core). /// public enum VexStatus { NotAffected, Affected, Fixed, UnderInvestigation } /// /// VEX observation for DeltaSig bridge (simplified model). /// public sealed record VexObservation { public required string ObservationId { get; init; } public required string TenantId { get; init; } public required string ProviderId { get; init; } public required string StreamId { get; init; } public required string Purl { get; init; } public required string CveId { get; init; } public required VexStatus Status { get; init; } public string? Justification { get; init; } public string? Impact { get; init; } public string? ActionStatement { get; init; } public DateTimeOffset ObservedAt { get; init; } public VexProvenance? Provenance { get; init; } public IReadOnlyList? Evidence { get; init; } public string? Supersedes { get; init; } public IReadOnlyDictionary? Metadata { get; init; } } /// /// VEX provenance metadata. /// public sealed record VexProvenance { public required string Source { get; init; } public required string Method { get; init; } public double Confidence { get; init; } public string? ToolVersion { get; init; } public string? SourceUri { get; init; } } /// /// VEX evidence block. /// public sealed record VexEvidenceBlock { public required string Type { get; init; } public required string Label { get; init; } public required string Content { get; init; } public string ContentType { get; init; } = "text/plain"; }