// SPDX-License-Identifier: BUSL-1.1 // Sprint: SPRINT_20260102_001_BE // Task: DS-041 - VEX evidence emission for backport detection using System.Collections.Immutable; using System.Text.Json.Serialization; namespace StellaOps.Scanner.Evidence.Models; /// /// Evidence from binary delta signature analysis. /// Provides cryptographic proof of backport status by comparing /// normalized binary code against known patched/vulnerable signatures. /// public sealed record DeltaSignatureEvidence { /// /// Schema version for this evidence type. /// [JsonPropertyName("schema")] public string Schema { get; init; } = "stellaops.evidence.deltasig.v1"; /// /// Overall result: patched, vulnerable, inconclusive. /// [JsonPropertyName("result")] public required DeltaSigResult Result { get; init; } /// /// CVE identifier(s) that were evaluated. /// [JsonPropertyName("cveIds")] public required ImmutableArray CveIds { get; init; } /// /// Package reference (PURL) being analyzed. /// [JsonPropertyName("packagePurl")] public required string PackagePurl { get; init; } /// /// Binary identifier (path or Build-ID). /// [JsonPropertyName("binaryId")] public required string BinaryId { get; init; } /// /// Target architecture (x86_64, aarch64, etc.). /// [JsonPropertyName("architecture")] public required string Architecture { get; init; } /// /// Symbol-level match results. /// [JsonPropertyName("symbolMatches")] public required ImmutableArray SymbolMatches { get; init; } /// /// Confidence score [0, 1] of the overall determination. /// [JsonPropertyName("confidence")] public required decimal Confidence { get; init; } /// /// Normalization recipe used for comparison. /// [JsonPropertyName("normalizationRecipe")] public required NormalizationRecipeRef NormalizationRecipe { get; init; } /// /// When this evidence was generated. /// [JsonPropertyName("generatedAt")] public required DateTimeOffset GeneratedAt { get; init; } /// /// Human-readable summary of the determination. /// [JsonPropertyName("summary")] public required string Summary { get; init; } /// /// Optional DSSE envelope URI containing the signed attestation. /// [JsonPropertyName("attestationUri")] public string? AttestationUri { get; init; } /// /// Creates evidence for a patched binary. /// public static DeltaSignatureEvidence CreatePatched( ImmutableArray cveIds, string packagePurl, string binaryId, string architecture, ImmutableArray symbolMatches, decimal confidence, NormalizationRecipeRef recipe, TimeProvider? timeProvider = null) { var matchSummary = symbolMatches.IsDefaultOrEmpty ? "No symbols analyzed" : $"{symbolMatches.Count(m => m.State == SignatureState.Patched)} patched, " + $"{symbolMatches.Count(m => m.State == SignatureState.Vulnerable)} vulnerable"; return new DeltaSignatureEvidence { Result = DeltaSigResult.Patched, CveIds = cveIds, PackagePurl = packagePurl, BinaryId = binaryId, Architecture = architecture, SymbolMatches = symbolMatches, Confidence = confidence, NormalizationRecipe = recipe, GeneratedAt = (timeProvider ?? TimeProvider.System).GetUtcNow(), Summary = $"Binary confirmed PATCHED with {confidence:P0} confidence. {matchSummary}." }; } /// /// Creates evidence for a vulnerable binary. /// public static DeltaSignatureEvidence CreateVulnerable( ImmutableArray cveIds, string packagePurl, string binaryId, string architecture, ImmutableArray symbolMatches, decimal confidence, NormalizationRecipeRef recipe, TimeProvider? timeProvider = null) { var matchSummary = symbolMatches.IsDefaultOrEmpty ? "No symbols analyzed" : $"{symbolMatches.Count(m => m.State == SignatureState.Vulnerable)} vulnerable symbols matched"; return new DeltaSignatureEvidence { Result = DeltaSigResult.Vulnerable, CveIds = cveIds, PackagePurl = packagePurl, BinaryId = binaryId, Architecture = architecture, SymbolMatches = symbolMatches, Confidence = confidence, NormalizationRecipe = recipe, GeneratedAt = (timeProvider ?? TimeProvider.System).GetUtcNow(), Summary = $"Binary confirmed VULNERABLE with {confidence:P0} confidence. {matchSummary}." }; } /// /// Creates evidence for an inconclusive analysis. /// public static DeltaSignatureEvidence CreateInconclusive( ImmutableArray cveIds, string packagePurl, string binaryId, string architecture, string reason, ImmutableArray symbolMatches, NormalizationRecipeRef recipe, TimeProvider? timeProvider = null) { return new DeltaSignatureEvidence { Result = DeltaSigResult.Inconclusive, CveIds = cveIds, PackagePurl = packagePurl, BinaryId = binaryId, Architecture = architecture, SymbolMatches = symbolMatches, Confidence = 0m, NormalizationRecipe = recipe, GeneratedAt = (timeProvider ?? TimeProvider.System).GetUtcNow(), Summary = $"Analysis INCONCLUSIVE: {reason}" }; } } /// /// Result of delta signature analysis. /// [JsonConverter(typeof(JsonStringEnumConverter))] public enum DeltaSigResult { /// /// Binary contains patched code - security fix is applied. /// [JsonStringEnumMemberName("patched")] Patched, /// /// Binary contains vulnerable code - security fix is NOT applied. /// [JsonStringEnumMemberName("vulnerable")] Vulnerable, /// /// Unable to determine patch status (symbols not found, LTO obfuscation, etc.). /// [JsonStringEnumMemberName("inconclusive")] Inconclusive } /// /// Evidence for a single symbol match. /// public sealed record SymbolMatchEvidence { /// /// Symbol/function name. /// [JsonPropertyName("symbolName")] public required string SymbolName { get; init; } /// /// Normalized hash of the symbol (SHA-256). /// [JsonPropertyName("hashHex")] public required string HashHex { get; init; } /// /// Matched signature state (patched or vulnerable). /// [JsonPropertyName("state")] public required SignatureState State { get; init; } /// /// Match confidence [0, 1]. /// [JsonPropertyName("confidence")] public required decimal Confidence { get; init; } /// /// Whether this was an exact full-hash match. /// [JsonPropertyName("exactMatch")] public required bool ExactMatch { get; init; } /// /// Number of chunks matched (for partial matching). /// [JsonPropertyName("chunksMatched")] public int? ChunksMatched { get; init; } /// /// Total chunks in signature. /// [JsonPropertyName("chunksTotal")] public int? ChunksTotal { get; init; } /// /// Reference to the matched signature (PURL of the signature source package). /// [JsonPropertyName("signatureSourcePurl")] public string? SignatureSourcePurl { get; init; } } /// /// State of a signature: vulnerable or patched. /// [JsonConverter(typeof(JsonStringEnumConverter))] public enum SignatureState { /// /// Signature represents vulnerable code (before fix). /// [JsonStringEnumMemberName("vulnerable")] Vulnerable, /// /// Signature represents patched code (after fix). /// [JsonStringEnumMemberName("patched")] Patched } /// /// Reference to the normalization recipe used. /// public sealed record NormalizationRecipeRef { /// /// Recipe identifier (e.g., "stellaops.normalize.x64.v1"). /// [JsonPropertyName("recipeId")] public required string RecipeId { get; init; } /// /// Recipe version. /// [JsonPropertyName("version")] public required string Version { get; init; } /// /// Normalization steps applied. /// [JsonPropertyName("steps")] public ImmutableArray Steps { get; init; } = []; }