Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Confidence/VexTrustConfidenceFactorProvider.cs
2026-02-01 21:37:40 +02:00

206 lines
5.5 KiB
C#

// VexTrustConfidenceFactorProvider - Confidence factor from VEX trust data
// Part of SPRINT_1227_0004_0003: VexTrustGate Policy Integration
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Engine.Gates;
using System;
using System.Collections.Generic;
namespace StellaOps.Policy.Engine.Confidence;
/// <summary>
/// Interface for providers that contribute confidence factors.
/// </summary>
public interface IConfidenceFactorProvider
{
/// <summary>
/// The type of confidence factor this provider produces.
/// </summary>
ConfidenceFactorType Type { get; }
/// <summary>
/// Compute a confidence factor from available context.
/// Returns null if insufficient data to compute.
/// </summary>
ConfidenceFactor? ComputeFactor(
ConfidenceFactorContext context,
ConfidenceFactorOptions options);
}
/// <summary>
/// Context for confidence factor computation.
/// </summary>
public sealed record ConfidenceFactorContext
{
/// <summary>
/// VEX trust status from signature verification.
/// </summary>
public VexTrustStatus? VexTrustStatus { get; init; }
/// <summary>
/// Environment (production, staging, development).
/// </summary>
public string? Environment { get; init; }
/// <summary>
/// Tenant identifier.
/// </summary>
public string? TenantId { get; init; }
/// <summary>
/// CVE or vulnerability identifier.
/// </summary>
public string? VulnerabilityId { get; init; }
/// <summary>
/// Product/component identifier.
/// </summary>
public string? ProductId { get; init; }
}
/// <summary>
/// Options for confidence factor computation.
/// </summary>
public sealed record ConfidenceFactorOptions
{
/// <summary>
/// Weight to assign to VEX trust factor.
/// </summary>
public decimal VexTrustWeight { get; init; } = 0.20m;
/// <summary>
/// Minimum trust score to contribute positively.
/// </summary>
public decimal MinTrustScoreContribution { get; init; } = 0.30m;
/// <summary>
/// Bonus for signature verification.
/// </summary>
public decimal SignatureVerifiedBonus { get; init; } = 0.10m;
/// <summary>
/// Bonus for Rekor transparency.
/// </summary>
public decimal TransparencyBonus { get; init; } = 0.05m;
}
/// <summary>
/// Computes VEX trust confidence factor from signature verification results.
/// </summary>
public sealed class VexTrustConfidenceFactorProvider : IConfidenceFactorProvider
{
public ConfidenceFactorType Type => ConfidenceFactorType.Vex;
/// <inheritdoc />
public ConfidenceFactor? ComputeFactor(
ConfidenceFactorContext context,
ConfidenceFactorOptions options)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(options);
var trustStatus = context.VexTrustStatus;
// No trust status means we can't contribute a factor
if (trustStatus is null)
{
return null;
}
var score = trustStatus.TrustScore;
var tier = ComputeTier(score);
// Apply bonuses for verification and transparency
var adjustedScore = score;
if (trustStatus.SignatureVerified == true)
{
adjustedScore += options.SignatureVerifiedBonus;
}
if (trustStatus.RekorLogIndex.HasValue)
{
adjustedScore += options.TransparencyBonus;
}
// Clamp to [0, 1]
adjustedScore = Math.Clamp(adjustedScore, 0m, 1m);
return new ConfidenceFactor
{
Type = ConfidenceFactorType.Vex,
Weight = options.VexTrustWeight,
RawValue = adjustedScore,
Reason = BuildReason(trustStatus, tier),
EvidenceDigests = BuildEvidenceDigests(trustStatus)
};
}
private static string ComputeTier(decimal score)
{
return score switch
{
>= 0.9m => "VeryHigh",
>= 0.7m => "High",
>= 0.5m => "Medium",
>= 0.3m => "Low",
_ => "VeryLow"
};
}
private static string BuildReason(VexTrustStatus status, string tier)
{
var parts = new List<string>
{
$"VEX trust: {tier} ({status.TrustScore:P0})"
};
if (!string.IsNullOrEmpty(status.IssuerName))
{
parts.Add($"issuer: {status.IssuerName}");
}
if (status.SignatureVerified == true)
{
parts.Add("signature verified");
if (!string.IsNullOrEmpty(status.SignatureMethod))
{
parts.Add($"method: {status.SignatureMethod}");
}
}
if (status.RekorLogIndex.HasValue)
{
parts.Add($"Rekor: #{status.RekorLogIndex}");
}
if (!string.IsNullOrEmpty(status.Freshness))
{
parts.Add($"freshness: {status.Freshness}");
}
return string.Join("; ", parts);
}
private static IReadOnlyList<string> BuildEvidenceDigests(VexTrustStatus status)
{
var digests = new List<string>();
// Add Rekor log info as an evidence reference
if (!string.IsNullOrEmpty(status.RekorLogId) && status.RekorLogIndex.HasValue)
{
digests.Add($"rekor:{status.RekorLogId}@{status.RekorLogIndex}");
}
// Add issuer reference
if (!string.IsNullOrEmpty(status.IssuerId))
{
digests.Add($"issuer:{status.IssuerId}");
}
return digests;
}
}