sprints and audit work
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy.Determinization;
|
||||
using StellaOps.Policy.Determinization.Models;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Policies;
|
||||
|
||||
/// <summary>
|
||||
/// Implements allow/quarantine/escalate logic per advisory specification.
|
||||
/// </summary>
|
||||
public sealed class DeterminizationPolicy : IDeterminizationPolicy
|
||||
{
|
||||
private readonly DeterminizationOptions _options;
|
||||
private readonly DeterminizationRuleSet _ruleSet;
|
||||
private readonly ILogger<DeterminizationPolicy> _logger;
|
||||
|
||||
public DeterminizationPolicy(
|
||||
IOptions<DeterminizationOptions> options,
|
||||
ILogger<DeterminizationPolicy> logger)
|
||||
{
|
||||
_options = options.Value;
|
||||
_ruleSet = DeterminizationRuleSet.Default(_options);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public DeterminizationResult Evaluate(DeterminizationContext ctx)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ctx);
|
||||
|
||||
// Get environment-specific thresholds
|
||||
var thresholds = GetEnvironmentThresholds(ctx.Environment);
|
||||
|
||||
// Evaluate rules in priority order
|
||||
foreach (var rule in _ruleSet.Rules.OrderBy(r => r.Priority))
|
||||
{
|
||||
if (rule.Condition(ctx, thresholds))
|
||||
{
|
||||
var result = rule.Action(ctx, thresholds);
|
||||
result = result with { MatchedRule = rule.Name };
|
||||
|
||||
_logger.LogDebug(
|
||||
"Rule {RuleName} matched for CVE {CveId}: {Status}",
|
||||
rule.Name,
|
||||
ctx.SignalSnapshot.Cve,
|
||||
result.Status);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: Deferred (no rule matched, needs more evidence)
|
||||
return DeterminizationResult.Deferred(
|
||||
"No determinization rule matched; additional evidence required");
|
||||
}
|
||||
|
||||
private EnvironmentThresholds GetEnvironmentThresholds(DeploymentEnvironment env)
|
||||
{
|
||||
return env switch
|
||||
{
|
||||
DeploymentEnvironment.Production => DefaultEnvironmentThresholds.Production,
|
||||
DeploymentEnvironment.Staging => DefaultEnvironmentThresholds.Staging,
|
||||
DeploymentEnvironment.Testing => DefaultEnvironmentThresholds.Development,
|
||||
DeploymentEnvironment.Development => DefaultEnvironmentThresholds.Development,
|
||||
_ => DefaultEnvironmentThresholds.Development
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment-specific thresholds for determinization decisions.
|
||||
/// </summary>
|
||||
public sealed record EnvironmentThresholds
|
||||
{
|
||||
public required DeploymentEnvironment Environment { get; init; }
|
||||
public required double MinConfidenceForNotAffected { get; init; }
|
||||
public required double MaxEntropyForAllow { get; init; }
|
||||
public required double EpssBlockThreshold { get; init; }
|
||||
public required bool RequireReachabilityForAllow { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default environment thresholds per advisory.
|
||||
/// </summary>
|
||||
public static class DefaultEnvironmentThresholds
|
||||
{
|
||||
public static EnvironmentThresholds Production => new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Production,
|
||||
MinConfidenceForNotAffected = 0.75,
|
||||
MaxEntropyForAllow = 0.3,
|
||||
EpssBlockThreshold = 0.3,
|
||||
RequireReachabilityForAllow = true
|
||||
};
|
||||
|
||||
public static EnvironmentThresholds Staging => new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Staging,
|
||||
MinConfidenceForNotAffected = 0.60,
|
||||
MaxEntropyForAllow = 0.5,
|
||||
EpssBlockThreshold = 0.4,
|
||||
RequireReachabilityForAllow = true
|
||||
};
|
||||
|
||||
public static EnvironmentThresholds Development => new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Development,
|
||||
MinConfidenceForNotAffected = 0.40,
|
||||
MaxEntropyForAllow = 0.7,
|
||||
EpssBlockThreshold = 0.6,
|
||||
RequireReachabilityForAllow = false
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Determinization;
|
||||
using StellaOps.Policy.Determinization.Models;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Policies;
|
||||
|
||||
/// <summary>
|
||||
/// Rule set for determinization policy evaluation.
|
||||
/// Rules are evaluated in priority order (lower = higher priority).
|
||||
/// </summary>
|
||||
public sealed class DeterminizationRuleSet
|
||||
{
|
||||
public IReadOnlyList<DeterminizationRule> Rules { get; }
|
||||
|
||||
private DeterminizationRuleSet(IReadOnlyList<DeterminizationRule> rules)
|
||||
{
|
||||
Rules = rules;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the default rule set per advisory specification.
|
||||
/// </summary>
|
||||
public static DeterminizationRuleSet Default(DeterminizationOptions options) =>
|
||||
new(new List<DeterminizationRule>
|
||||
{
|
||||
// Rule 1: Escalate if runtime evidence shows vulnerable code loaded
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "RuntimeEscalation",
|
||||
Priority = 10,
|
||||
Condition = (ctx, _) =>
|
||||
ctx.SignalSnapshot.Runtime.HasValue &&
|
||||
ctx.SignalSnapshot.Runtime.Value!.ObservedLoaded,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Escalated(
|
||||
"Runtime evidence shows vulnerable code loaded in memory")
|
||||
},
|
||||
|
||||
// Rule 2: Quarantine if EPSS exceeds threshold
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "EpssQuarantine",
|
||||
Priority = 20,
|
||||
Condition = (ctx, thresholds) =>
|
||||
ctx.SignalSnapshot.Epss.HasValue &&
|
||||
ctx.SignalSnapshot.Epss.Value!.Score >= thresholds.EpssBlockThreshold,
|
||||
Action = (ctx, thresholds) =>
|
||||
DeterminizationResult.Quarantined(
|
||||
$"EPSS score {ctx.SignalSnapshot.Epss.Value!.Score:P1} exceeds threshold {thresholds.EpssBlockThreshold:P1}")
|
||||
},
|
||||
|
||||
// Rule 3: Quarantine if proven reachable
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "ReachabilityQuarantine",
|
||||
Priority = 25,
|
||||
Condition = (ctx, _) =>
|
||||
ctx.SignalSnapshot.Reachability.HasValue &&
|
||||
ctx.SignalSnapshot.Reachability.Value!.IsReachable,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Quarantined(
|
||||
$"Vulnerable code is reachable via call graph analysis")
|
||||
},
|
||||
|
||||
// Rule 4: Block high entropy in production
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "ProductionEntropyBlock",
|
||||
Priority = 30,
|
||||
Condition = (ctx, thresholds) =>
|
||||
ctx.Environment == DeploymentEnvironment.Production &&
|
||||
ctx.UncertaintyScore.Entropy > thresholds.MaxEntropyForAllow,
|
||||
Action = (ctx, thresholds) =>
|
||||
DeterminizationResult.Quarantined(
|
||||
$"High uncertainty (entropy={ctx.UncertaintyScore.Entropy:F2}) exceeds production threshold ({thresholds.MaxEntropyForAllow:F2})")
|
||||
},
|
||||
|
||||
// Rule 5: Defer if evidence is stale
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "StaleEvidenceDefer",
|
||||
Priority = 40,
|
||||
Condition = (ctx, _) => ctx.Decay.IsStale,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Deferred(
|
||||
$"Evidence is stale (last update: {ctx.Decay.LastSignalUpdate:u}, age: {ctx.Decay.AgeDays:F1} days)")
|
||||
},
|
||||
|
||||
// Rule 6: Guarded allow for uncertain observations in non-prod
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "GuardedAllowNonProd",
|
||||
Priority = 50,
|
||||
Condition = (ctx, _) =>
|
||||
ctx.TrustScore < 0.5 &&
|
||||
ctx.UncertaintyScore.Entropy > 0.4 &&
|
||||
ctx.Environment != DeploymentEnvironment.Production,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.GuardedPass(
|
||||
$"Uncertain observation (entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}) allowed with guardrails in {ctx.Environment}",
|
||||
BuildGuardrails(ctx, GuardRailsLevel.Moderate))
|
||||
},
|
||||
|
||||
// Rule 7: Allow if unreachable with high confidence
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "UnreachableAllow",
|
||||
Priority = 60,
|
||||
Condition = (ctx, thresholds) =>
|
||||
ctx.SignalSnapshot.Reachability.HasValue &&
|
||||
!ctx.SignalSnapshot.Reachability.Value!.IsReachable &&
|
||||
ctx.SignalSnapshot.Reachability.Value.Confidence >= thresholds.MinConfidenceForNotAffected,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Allowed(
|
||||
$"Vulnerable code is unreachable (confidence={ctx.SignalSnapshot.Reachability.Value!.Confidence:P0})")
|
||||
},
|
||||
|
||||
// Rule 8: Allow if VEX not_affected with trusted issuer
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "VexNotAffectedAllow",
|
||||
Priority = 65,
|
||||
Condition = (ctx, thresholds) =>
|
||||
ctx.SignalSnapshot.Vex.HasValue &&
|
||||
ctx.SignalSnapshot.Vex.Value!.IsNotAffected &&
|
||||
ctx.SignalSnapshot.Vex.Value.IssuerTrust >= thresholds.MinConfidenceForNotAffected,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Allowed(
|
||||
$"VEX statement indicates not_affected (trust={ctx.SignalSnapshot.Vex.Value!.IssuerTrust:P0})")
|
||||
},
|
||||
|
||||
// Rule 9: Allow if sufficient evidence and low entropy
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "SufficientEvidenceAllow",
|
||||
Priority = 70,
|
||||
Condition = (ctx, thresholds) =>
|
||||
ctx.UncertaintyScore.Entropy <= thresholds.MaxEntropyForAllow &&
|
||||
ctx.TrustScore >= thresholds.MinConfidenceForNotAffected,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Allowed(
|
||||
$"Sufficient evidence (entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}) for confident determination")
|
||||
},
|
||||
|
||||
// Rule 10: Guarded allow for moderate uncertainty
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "GuardedAllowModerateUncertainty",
|
||||
Priority = 80,
|
||||
Condition = (ctx, _) =>
|
||||
ctx.UncertaintyScore.Tier <= UncertaintyTier.Moderate &&
|
||||
ctx.TrustScore >= 0.4,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.GuardedPass(
|
||||
$"Moderate uncertainty (tier={ctx.UncertaintyScore.Tier}, trust={ctx.TrustScore:F2}) allowed with monitoring",
|
||||
BuildGuardrails(ctx, GuardRailsLevel.Light))
|
||||
},
|
||||
|
||||
// Rule 11: Default - require more evidence
|
||||
new DeterminizationRule
|
||||
{
|
||||
Name = "DefaultDefer",
|
||||
Priority = 100,
|
||||
Condition = (_, _) => true,
|
||||
Action = (ctx, _) =>
|
||||
DeterminizationResult.Deferred(
|
||||
$"Insufficient evidence for determination (entropy={ctx.UncertaintyScore.Entropy:F2}, tier={ctx.UncertaintyScore.Tier})")
|
||||
}
|
||||
});
|
||||
|
||||
private enum GuardRailsLevel { Light, Moderate, Strict }
|
||||
|
||||
private static GuardRails BuildGuardrails(DeterminizationContext ctx, GuardRailsLevel level) =>
|
||||
level switch
|
||||
{
|
||||
GuardRailsLevel.Light => new GuardRails
|
||||
{
|
||||
EnableMonitoring = true,
|
||||
RestrictToNonProd = false,
|
||||
RequireApproval = false,
|
||||
ReevalAfter = TimeSpan.FromDays(14),
|
||||
Notes = $"Light guardrails: entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}, env={ctx.Environment}"
|
||||
},
|
||||
GuardRailsLevel.Moderate => new GuardRails
|
||||
{
|
||||
EnableMonitoring = true,
|
||||
RestrictToNonProd = ctx.Environment == DeploymentEnvironment.Production,
|
||||
RequireApproval = false,
|
||||
ReevalAfter = TimeSpan.FromDays(7),
|
||||
Notes = $"Moderate guardrails: entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}, env={ctx.Environment}"
|
||||
},
|
||||
GuardRailsLevel.Strict => new GuardRails
|
||||
{
|
||||
EnableMonitoring = true,
|
||||
RestrictToNonProd = true,
|
||||
RequireApproval = true,
|
||||
ReevalAfter = TimeSpan.FromDays(3),
|
||||
Notes = $"Strict guardrails: entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}, env={ctx.Environment}"
|
||||
},
|
||||
_ => GuardRails.Default()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single determinization rule.
|
||||
/// </summary>
|
||||
public sealed record DeterminizationRule
|
||||
{
|
||||
/// <summary>Rule name for audit/logging.</summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>Priority (lower = evaluated first).</summary>
|
||||
public required int Priority { get; init; }
|
||||
|
||||
/// <summary>Condition function.</summary>
|
||||
public required Func<DeterminizationContext, EnvironmentThresholds, bool> Condition { get; init; }
|
||||
|
||||
/// <summary>Action function.</summary>
|
||||
public required Func<DeterminizationContext, EnvironmentThresholds, DeterminizationResult> Action { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Determinization.Models;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Policies;
|
||||
|
||||
/// <summary>
|
||||
/// Policy for evaluating determinization decisions (allow/quarantine/escalate).
|
||||
/// </summary>
|
||||
public interface IDeterminizationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluate a CVE observation against determinization rules.
|
||||
/// </summary>
|
||||
/// <param name="context">Determinization context.</param>
|
||||
/// <returns>Policy decision result.</returns>
|
||||
DeterminizationResult Evaluate(DeterminizationContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of determinization policy evaluation.
|
||||
/// </summary>
|
||||
public sealed record DeterminizationResult
|
||||
{
|
||||
/// <summary>Policy verdict status.</summary>
|
||||
public required PolicyVerdictStatus Status { get; init; }
|
||||
|
||||
/// <summary>Explanation of the decision.</summary>
|
||||
public required string Reason { get; init; }
|
||||
|
||||
/// <summary>Guardrails if GuardedPass.</summary>
|
||||
public GuardRails? GuardRails { get; init; }
|
||||
|
||||
/// <summary>Rule that matched.</summary>
|
||||
public string? MatchedRule { get; init; }
|
||||
|
||||
/// <summary>Suggested observation state.</summary>
|
||||
public ObservationState? SuggestedState { get; init; }
|
||||
|
||||
public static DeterminizationResult Allowed(string reason) =>
|
||||
new() { Status = PolicyVerdictStatus.Pass, Reason = reason };
|
||||
|
||||
public static DeterminizationResult GuardedPass(string reason, GuardRails guardRails) =>
|
||||
new() { Status = PolicyVerdictStatus.GuardedPass, Reason = reason, GuardRails = guardRails };
|
||||
|
||||
public static DeterminizationResult Quarantined(string reason, PolicyVerdictStatus status = PolicyVerdictStatus.Blocked) =>
|
||||
new() { Status = status, Reason = reason };
|
||||
|
||||
public static DeterminizationResult Escalated(string reason, PolicyVerdictStatus status = PolicyVerdictStatus.Escalated) =>
|
||||
new() { Status = status, Reason = reason };
|
||||
|
||||
public static DeterminizationResult Deferred(string reason, PolicyVerdictStatus status = PolicyVerdictStatus.Deferred) =>
|
||||
new() { Status = status, Reason = reason };
|
||||
}
|
||||
Reference in New Issue
Block a user