release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -432,7 +432,7 @@ public sealed class OpenVexNormalizer : IVexNormalizer
|
||||
return null;
|
||||
}
|
||||
|
||||
var fallbackGuid = guidProvider?.NewGuid() ?? Guid.NewGuid();
|
||||
var fallbackGuid = (guidProvider ?? SystemGuidProvider.Instance).NewGuid();
|
||||
return new NormalizedProduct(
|
||||
Key: key ?? purl ?? cpe ?? $"unknown-{fallbackGuid:N}",
|
||||
Name: name,
|
||||
|
||||
@@ -144,7 +144,31 @@ public enum TrustFactor
|
||||
/// <summary>
|
||||
/// Product authority match.
|
||||
/// </summary>
|
||||
ProductAuthority
|
||||
ProductAuthority,
|
||||
|
||||
// --- Patch Verification Factors ---
|
||||
|
||||
/// <summary>
|
||||
/// Function-level binary fingerprint match confirms patch.
|
||||
/// Highest confidence patch verification method.
|
||||
/// </summary>
|
||||
PatchFunctionMatch,
|
||||
|
||||
/// <summary>
|
||||
/// Section-level binary hash match confirms patch.
|
||||
/// Medium confidence patch verification method.
|
||||
/// </summary>
|
||||
PatchSectionMatch,
|
||||
|
||||
/// <summary>
|
||||
/// Patch attestation signed by trusted issuer (DSSE).
|
||||
/// </summary>
|
||||
PatchIssuerAuthority,
|
||||
|
||||
/// <summary>
|
||||
/// Runtime symbols confirm patched version loaded.
|
||||
/// </summary>
|
||||
PatchRuntimeConfirm
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +226,12 @@ public sealed record TrustConfiguration
|
||||
[TrustFactor.Freshness] = 0.15,
|
||||
[TrustFactor.StatusQuality] = 0.10,
|
||||
[TrustFactor.SourceMatch] = 0.05,
|
||||
[TrustFactor.ProductAuthority] = 0.05
|
||||
[TrustFactor.ProductAuthority] = 0.05,
|
||||
// Patch verification factors (optional, applied when evidence present)
|
||||
[TrustFactor.PatchFunctionMatch] = 0.20,
|
||||
[TrustFactor.PatchSectionMatch] = 0.15,
|
||||
[TrustFactor.PatchIssuerAuthority] = 0.15,
|
||||
[TrustFactor.PatchRuntimeConfirm] = 0.10
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
namespace StellaOps.VexLens.Core.Trust;
|
||||
|
||||
/// <summary>
|
||||
/// Provides trust weight computation for patch verification evidence.
|
||||
/// Converts binary fingerprint match results into trust factors for VEX consensus.
|
||||
/// </summary>
|
||||
public sealed class PatchVerificationTrustProvider
|
||||
{
|
||||
private readonly TrustConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance with the specified configuration.
|
||||
/// </summary>
|
||||
public PatchVerificationTrustProvider(TrustConfiguration? configuration = null)
|
||||
{
|
||||
_configuration = configuration ?? TrustConfiguration.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes trust weight contribution from patch verification evidence.
|
||||
/// </summary>
|
||||
/// <param name="evidence">Patch verification evidence.</param>
|
||||
/// <returns>Trust weight with factor breakdown.</returns>
|
||||
public TrustWeight ComputeWeight(PatchVerificationTrustContext evidence)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evidence);
|
||||
|
||||
var factors = new Dictionary<TrustFactor, double>();
|
||||
var warnings = new List<string>();
|
||||
var totalWeight = 0.0;
|
||||
|
||||
// Function-level match (highest confidence)
|
||||
if (evidence.HasFunctionLevelMatch)
|
||||
{
|
||||
var factorWeight = GetFactorWeight(TrustFactor.PatchFunctionMatch);
|
||||
var contribution = factorWeight * evidence.FunctionMatchConfidence;
|
||||
factors[TrustFactor.PatchFunctionMatch] = contribution;
|
||||
totalWeight += contribution;
|
||||
}
|
||||
|
||||
// Section-level match
|
||||
if (evidence.HasSectionLevelMatch)
|
||||
{
|
||||
var factorWeight = GetFactorWeight(TrustFactor.PatchSectionMatch);
|
||||
var contribution = factorWeight * evidence.SectionMatchConfidence;
|
||||
factors[TrustFactor.PatchSectionMatch] = contribution;
|
||||
totalWeight += contribution;
|
||||
}
|
||||
|
||||
// Issuer authority (DSSE attestation)
|
||||
if (evidence.HasSignedAttestation)
|
||||
{
|
||||
var factorWeight = GetFactorWeight(TrustFactor.PatchIssuerAuthority);
|
||||
var contribution = factorWeight * evidence.IssuerAuthorityScore;
|
||||
factors[TrustFactor.PatchIssuerAuthority] = contribution;
|
||||
totalWeight += contribution;
|
||||
}
|
||||
else if (evidence.Status == PatchVerificationTrustStatus.Verified)
|
||||
{
|
||||
// Warn about missing attestation for verified patches
|
||||
warnings.Add("Patch verified but no DSSE attestation present");
|
||||
}
|
||||
|
||||
// Runtime confirmation (if available)
|
||||
if (evidence.HasRuntimeConfirmation)
|
||||
{
|
||||
var factorWeight = GetFactorWeight(TrustFactor.PatchRuntimeConfirm);
|
||||
var contribution = factorWeight * evidence.RuntimeConfirmationConfidence;
|
||||
factors[TrustFactor.PatchRuntimeConfirm] = contribution;
|
||||
totalWeight += contribution;
|
||||
}
|
||||
|
||||
// Apply status-based adjustments
|
||||
totalWeight = AdjustForStatus(totalWeight, evidence.Status, warnings);
|
||||
|
||||
// Clamp final weight
|
||||
totalWeight = Math.Clamp(totalWeight, 0.0, 1.0);
|
||||
|
||||
return new TrustWeight
|
||||
{
|
||||
Weight = totalWeight,
|
||||
Factors = factors,
|
||||
Explanation = GenerateExplanation(evidence, totalWeight),
|
||||
Warnings = warnings.Count > 0 ? warnings : null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes aggregate trust weight from multiple evidence items.
|
||||
/// </summary>
|
||||
public TrustWeight ComputeAggregateWeight(IEnumerable<PatchVerificationTrustContext> evidenceItems)
|
||||
{
|
||||
var items = evidenceItems.ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return new TrustWeight
|
||||
{
|
||||
Weight = 0.0,
|
||||
Factors = new Dictionary<TrustFactor, double>(),
|
||||
Explanation = "No patch verification evidence available"
|
||||
};
|
||||
}
|
||||
|
||||
// Compute individual weights
|
||||
var weights = items.Select(ComputeWeight).ToList();
|
||||
|
||||
// Aggregate factors (average of non-zero contributions)
|
||||
var aggregatedFactors = new Dictionary<TrustFactor, double>();
|
||||
foreach (var factor in Enum.GetValues<TrustFactor>())
|
||||
{
|
||||
var factorValues = weights
|
||||
.Where(w => w.Factors.ContainsKey(factor))
|
||||
.Select(w => w.Factors[factor])
|
||||
.ToList();
|
||||
|
||||
if (factorValues.Count > 0)
|
||||
{
|
||||
aggregatedFactors[factor] = factorValues.Average();
|
||||
}
|
||||
}
|
||||
|
||||
// Final weight: weighted average by confidence
|
||||
var totalWeight = weights.Average(w => w.Weight);
|
||||
|
||||
// Aggregate warnings
|
||||
var allWarnings = weights
|
||||
.Where(w => w.Warnings is not null)
|
||||
.SelectMany(w => w.Warnings!)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return new TrustWeight
|
||||
{
|
||||
Weight = totalWeight,
|
||||
Factors = aggregatedFactors,
|
||||
Explanation = $"Aggregate of {items.Count} patch verification evidence items",
|
||||
Warnings = allWarnings.Count > 0 ? allWarnings : null
|
||||
};
|
||||
}
|
||||
|
||||
private double GetFactorWeight(TrustFactor factor)
|
||||
{
|
||||
return _configuration.FactorWeights.TryGetValue(factor, out var weight)
|
||||
? weight
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
private static double AdjustForStatus(
|
||||
double weight,
|
||||
PatchVerificationTrustStatus status,
|
||||
List<string> warnings)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
PatchVerificationTrustStatus.Verified => weight,
|
||||
PatchVerificationTrustStatus.PartialMatch => weight * 0.6,
|
||||
PatchVerificationTrustStatus.Inconclusive =>
|
||||
AddWarningAndReturn(warnings, "Patch verification inconclusive", weight * 0.2),
|
||||
PatchVerificationTrustStatus.NotPatched => 0.0,
|
||||
PatchVerificationTrustStatus.NoPatchData =>
|
||||
AddWarningAndReturn(warnings, "No patch signature data available", 0.0),
|
||||
_ => weight
|
||||
};
|
||||
}
|
||||
|
||||
private static double AddWarningAndReturn(List<string> warnings, string warning, double value)
|
||||
{
|
||||
warnings.Add(warning);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string GenerateExplanation(PatchVerificationTrustContext evidence, double weight)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (evidence.HasFunctionLevelMatch)
|
||||
{
|
||||
parts.Add($"function-level match ({evidence.FunctionMatchConfidence:P0})");
|
||||
}
|
||||
|
||||
if (evidence.HasSectionLevelMatch)
|
||||
{
|
||||
parts.Add($"section-level match ({evidence.SectionMatchConfidence:P0})");
|
||||
}
|
||||
|
||||
if (evidence.HasSignedAttestation)
|
||||
{
|
||||
parts.Add("signed attestation present");
|
||||
}
|
||||
|
||||
if (evidence.HasRuntimeConfirmation)
|
||||
{
|
||||
parts.Add("runtime confirmation");
|
||||
}
|
||||
|
||||
var statusStr = evidence.Status switch
|
||||
{
|
||||
PatchVerificationTrustStatus.Verified => "Patch verified",
|
||||
PatchVerificationTrustStatus.PartialMatch => "Partial patch match",
|
||||
PatchVerificationTrustStatus.Inconclusive => "Verification inconclusive",
|
||||
PatchVerificationTrustStatus.NotPatched => "Not patched",
|
||||
PatchVerificationTrustStatus.NoPatchData => "No patch data",
|
||||
_ => "Unknown status"
|
||||
};
|
||||
|
||||
if (parts.Count > 0)
|
||||
{
|
||||
return $"{statusStr} based on: {string.Join(", ", parts)}. Trust weight: {weight:P0}";
|
||||
}
|
||||
|
||||
return $"{statusStr}. Trust weight: {weight:P0}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for patch verification trust computation.
|
||||
/// Maps from Scanner.PatchVerification.Models to trust computation inputs.
|
||||
/// </summary>
|
||||
public sealed record PatchVerificationTrustContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Overall verification status.
|
||||
/// </summary>
|
||||
public required PatchVerificationTrustStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether function-level (CFG/instruction) match was performed.
|
||||
/// </summary>
|
||||
public bool HasFunctionLevelMatch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence of function-level match (0.0-1.0).
|
||||
/// </summary>
|
||||
public double FunctionMatchConfidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether section-level hash match was performed.
|
||||
/// </summary>
|
||||
public bool HasSectionLevelMatch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence of section-level match (0.0-1.0).
|
||||
/// </summary>
|
||||
public double SectionMatchConfidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether DSSE-signed attestation is present.
|
||||
/// </summary>
|
||||
public bool HasSignedAttestation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Authority score of the attestation issuer (0.0-1.0).
|
||||
/// Based on issuer directory trust tier.
|
||||
/// </summary>
|
||||
public double IssuerAuthorityScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether runtime symbol confirmation is available.
|
||||
/// </summary>
|
||||
public bool HasRuntimeConfirmation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence of runtime confirmation (0.0-1.0).
|
||||
/// </summary>
|
||||
public double RuntimeConfirmationConfidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVE identifier being verified.
|
||||
/// </summary>
|
||||
public string? CveId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Binary path that was verified.
|
||||
/// </summary>
|
||||
public string? BinaryPath { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch verification status for trust computation.
|
||||
/// Mirrors Scanner.PatchVerification.Models.PatchVerificationStatus.
|
||||
/// </summary>
|
||||
public enum PatchVerificationTrustStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Patch verified with high confidence.
|
||||
/// </summary>
|
||||
Verified,
|
||||
|
||||
/// <summary>
|
||||
/// Partial match - some evidence of patching.
|
||||
/// </summary>
|
||||
PartialMatch,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to determine patch status conclusively.
|
||||
/// </summary>
|
||||
Inconclusive,
|
||||
|
||||
/// <summary>
|
||||
/// Binary does not appear to be patched.
|
||||
/// </summary>
|
||||
NotPatched,
|
||||
|
||||
/// <summary>
|
||||
/// No patch signature data available for comparison.
|
||||
/// </summary>
|
||||
NoPatchData
|
||||
}
|
||||
Reference in New Issue
Block a user