sprints work.
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Bridges DeltaSig v2 predicates with VEX observations.
|
||||
/// </summary>
|
||||
public interface IDeltaSigVexBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a VEX observation from a DeltaSig v2 predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The v2 predicate.</param>
|
||||
/// <param name="context">VEX generation context.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>VEX observation.</returns>
|
||||
Task<VexObservation> GenerateFromPredicateAsync(
|
||||
DeltaSigPredicateV2 predicate,
|
||||
DeltaSigVexContext context,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a v2 predicate verdict to a VEX statement status.
|
||||
/// </summary>
|
||||
/// <param name="verdict">The DeltaSig verdict.</param>
|
||||
/// <returns>VEX statement status.</returns>
|
||||
VexStatus MapVerdictToStatus(string verdict);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts evidence blocks from a v2 predicate.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The v2 predicate.</param>
|
||||
/// <returns>Evidence blocks for VEX attachment.</returns>
|
||||
IReadOnlyList<VexEvidenceBlock> ExtractEvidence(DeltaSigPredicateV2 predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of DeltaSig-VEX bridge.
|
||||
/// </summary>
|
||||
public sealed class DeltaSigVexBridge : IDeltaSigVexBridge
|
||||
{
|
||||
private readonly ILogger<DeltaSigVexBridge> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new bridge instance.
|
||||
/// </summary>
|
||||
public DeltaSigVexBridge(
|
||||
ILogger<DeltaSigVexBridge> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<VexObservation> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<VexEvidenceBlock> ExtractEvidence(DeltaSigPredicateV2 predicate)
|
||||
{
|
||||
var blocks = new List<VexEvidenceBlock>();
|
||||
|
||||
// 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<string, string>? BuildMetadata(
|
||||
DeltaSigPredicateV2 predicate,
|
||||
DeltaSigVexContext context)
|
||||
{
|
||||
var metadata = new Dictionary<string, string>
|
||||
{
|
||||
["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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for DeltaSig VEX generation.
|
||||
/// </summary>
|
||||
public sealed record DeltaSigVexContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Tenant identifier.
|
||||
/// </summary>
|
||||
public required string TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional scan identifier.
|
||||
/// </summary>
|
||||
public string? ScanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional source URI for the predicate.
|
||||
/// </summary>
|
||||
public string? SourceUri { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional observation ID this supersedes.
|
||||
/// </summary>
|
||||
public string? SupersedesObservationId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX status enum (mirrors Excititor.Core).
|
||||
/// </summary>
|
||||
public enum VexStatus
|
||||
{
|
||||
NotAffected,
|
||||
Affected,
|
||||
Fixed,
|
||||
UnderInvestigation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX observation for DeltaSig bridge (simplified model).
|
||||
/// </summary>
|
||||
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<VexEvidenceBlock>? Evidence { get; init; }
|
||||
public string? Supersedes { get; init; }
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX provenance metadata.
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEX evidence block.
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
Reference in New Issue
Block a user