Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Gate/VexGatePolicyEvaluator.cs
2026-01-07 09:43:12 +02:00

117 lines
3.7 KiB
C#

// -----------------------------------------------------------------------------
// VexGatePolicyEvaluator.cs
// Sprint: SPRINT_20260106_003_002_SCANNER_vex_gate_service
// Description: Policy evaluator for VEX gate decisions.
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace StellaOps.Scanner.Gate;
/// <summary>
/// Default implementation of <see cref="IVexGatePolicy"/>.
/// Evaluates evidence against policy rules in priority order.
/// </summary>
public sealed class VexGatePolicyEvaluator : IVexGatePolicy
{
private readonly ILogger<VexGatePolicyEvaluator> _logger;
private readonly VexGatePolicy _policy;
public VexGatePolicyEvaluator(
IOptions<VexGatePolicyOptions> options,
ILogger<VexGatePolicyEvaluator> logger)
{
_logger = logger;
_policy = options.Value.Policy ?? VexGatePolicy.Default;
}
/// <summary>
/// Creates an evaluator with the default policy.
/// </summary>
public VexGatePolicyEvaluator(ILogger<VexGatePolicyEvaluator> logger)
{
_logger = logger;
_policy = VexGatePolicy.Default;
}
/// <inheritdoc />
public VexGatePolicy Policy => _policy;
/// <inheritdoc />
public (VexGateDecision Decision, string RuleId, string Rationale) Evaluate(VexGateEvidence evidence)
{
// Sort rules by priority descending and evaluate in order
var sortedRules = _policy.Rules
.OrderByDescending(r => r.Priority)
.ToList();
foreach (var rule in sortedRules)
{
if (rule.Condition.Matches(evidence))
{
var rationale = BuildRationale(rule, evidence);
_logger.LogDebug(
"VEX gate rule matched: {RuleId} -> {Decision} for evidence with vendor status {VendorStatus}",
rule.RuleId,
rule.Decision,
evidence.VendorStatus);
return (rule.Decision, rule.RuleId, rationale);
}
}
// No rule matched, return default
var defaultRationale = "No policy rule matched; applying default decision";
_logger.LogDebug(
"No VEX gate rule matched; defaulting to {Decision}",
_policy.DefaultDecision);
return (_policy.DefaultDecision, "default", defaultRationale);
}
private static string BuildRationale(VexGatePolicyRule rule, VexGateEvidence evidence)
{
return rule.RuleId switch
{
"block-exploitable-reachable" =>
"Exploitable + reachable, no compensating control",
"warn-high-not-reachable" =>
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"{0} severity but not reachable from entrypoints",
evidence.SeverityLevel ?? "High"),
"pass-vendor-not-affected" =>
"Vendor VEX statement declares not_affected",
"pass-backport-confirmed" =>
"Vendor VEX statement confirms fixed via backport",
_ => string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"Policy rule '{0}' matched",
rule.RuleId)
};
}
}
/// <summary>
/// Options for VEX gate policy configuration.
/// </summary>
public sealed class VexGatePolicyOptions
{
/// <summary>
/// Custom policy to use instead of default.
/// </summary>
public VexGatePolicy? Policy { get; set; }
/// <summary>
/// Whether the gate is enabled.
/// </summary>
public bool Enabled { get; set; } = true;
}