277 lines
8.6 KiB
C#
277 lines
8.6 KiB
C#
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Policy;
|
|
using StellaOps.Policy.Determinization;
|
|
using StellaOps.Policy.Determinization.Models;
|
|
using StellaOps.Policy.Engine.Policies;
|
|
|
|
namespace StellaOps.Policy.Engine.Tests.Policies;
|
|
|
|
public class DeterminizationPolicyTests
|
|
{
|
|
private readonly DeterminizationPolicy _policy;
|
|
|
|
public DeterminizationPolicyTests()
|
|
{
|
|
var options = Options.Create(new DeterminizationOptions());
|
|
_policy = new DeterminizationPolicy(options, NullLogger<DeterminizationPolicy>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_RuntimeEvidenceLoaded_ReturnsEscalated()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
runtime: new SignalState<RuntimeEvidence>
|
|
{
|
|
HasValue = true,
|
|
Value = new RuntimeEvidence { ObservedLoaded = true }
|
|
});
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Escalated);
|
|
result.MatchedRule.Should().Be("RuntimeEscalation");
|
|
result.Reason.Should().Contain("Runtime evidence shows vulnerable code loaded");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_HighEpss_ReturnsQuarantined()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
epss: new SignalState<EpssEvidence>
|
|
{
|
|
HasValue = true,
|
|
Value = new EpssEvidence { Score = 0.8 }
|
|
},
|
|
environment: DeploymentEnvironment.Production);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Blocked);
|
|
result.MatchedRule.Should().Be("EpssQuarantine");
|
|
result.Reason.Should().Contain("EPSS score");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_ReachableCode_ReturnsQuarantined()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
reachability: new SignalState<ReachabilityEvidence>
|
|
{
|
|
HasValue = true,
|
|
Value = new ReachabilityEvidence { IsReachable = true, Confidence = 0.9 }
|
|
});
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Blocked);
|
|
result.MatchedRule.Should().Be("ReachabilityQuarantine");
|
|
result.Reason.Should().Contain("reachable");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_HighEntropyInProduction_ReturnsQuarantined()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
entropy: 0.5,
|
|
environment: DeploymentEnvironment.Production);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Blocked);
|
|
result.MatchedRule.Should().Be("ProductionEntropyBlock");
|
|
result.Reason.Should().Contain("High uncertainty");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_StaleEvidence_ReturnsDeferred()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
isStale: true);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Deferred);
|
|
result.MatchedRule.Should().Be("StaleEvidenceDefer");
|
|
result.Reason.Should().Contain("stale");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_ModerateUncertaintyInDev_ReturnsGuardedPass()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
entropy: 0.5,
|
|
trustScore: 0.3,
|
|
environment: DeploymentEnvironment.Development);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.GuardedPass);
|
|
result.MatchedRule.Should().Be("GuardedAllowNonProd");
|
|
result.GuardRails.Should().NotBeNull();
|
|
result.GuardRails!.EnableMonitoring.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_UnreachableWithHighConfidence_ReturnsAllowed()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
reachability: new SignalState<ReachabilityEvidence>
|
|
{
|
|
HasValue = true,
|
|
Value = new ReachabilityEvidence { IsReachable = false, Confidence = 0.9 }
|
|
},
|
|
trustScore: 0.8);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Pass);
|
|
result.MatchedRule.Should().Be("UnreachableAllow");
|
|
result.Reason.Should().Contain("unreachable");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_VexNotAffected_ReturnsAllowed()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
vex: new SignalState<VexClaimSummary>
|
|
{
|
|
HasValue = true,
|
|
Value = new VexClaimSummary { IsNotAffected = true, IssuerTrust = 0.9 }
|
|
},
|
|
trustScore: 0.8);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Pass);
|
|
result.MatchedRule.Should().Be("VexNotAffectedAllow");
|
|
result.Reason.Should().Contain("not_affected");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_SufficientEvidenceLowEntropy_ReturnsAllowed()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
entropy: 0.2,
|
|
trustScore: 0.8,
|
|
environment: DeploymentEnvironment.Production);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Pass);
|
|
result.MatchedRule.Should().Be("SufficientEvidenceAllow");
|
|
result.Reason.Should().Contain("Sufficient evidence");
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_ModerateUncertaintyTier_ReturnsGuardedPass()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
tier: UncertaintyTier.Moderate,
|
|
trustScore: 0.5,
|
|
entropy: 0.5);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.GuardedPass);
|
|
result.MatchedRule.Should().Be("GuardedAllowModerateUncertainty");
|
|
result.GuardRails.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Evaluate_NoMatchingRule_ReturnsDeferred()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(
|
|
entropy: 0.9,
|
|
trustScore: 0.1,
|
|
environment: DeploymentEnvironment.Production);
|
|
|
|
// Act
|
|
var result = _policy.Evaluate(context);
|
|
|
|
// Assert
|
|
result.Status.Should().Be(PolicyVerdictStatus.Deferred);
|
|
result.MatchedRule.Should().Be("DefaultDefer");
|
|
result.Reason.Should().Contain("Insufficient evidence");
|
|
}
|
|
|
|
private static DeterminizationContext CreateContext(
|
|
SignalState<EpssEvidence>? epss = null,
|
|
SignalState<VexClaimSummary>? vex = null,
|
|
SignalState<ReachabilityEvidence>? reachability = null,
|
|
SignalState<RuntimeEvidence>? runtime = null,
|
|
double entropy = 0.0,
|
|
double trustScore = 0.0,
|
|
UncertaintyTier tier = UncertaintyTier.Minimal,
|
|
DeploymentEnvironment environment = DeploymentEnvironment.Development,
|
|
bool isStale = false)
|
|
{
|
|
var snapshot = new SignalSnapshot
|
|
{
|
|
Cve = "CVE-2024-0001",
|
|
Purl = "pkg:npm/test@1.0.0",
|
|
Epss = epss ?? SignalState<EpssEvidence>.NotQueried(),
|
|
Vex = vex ?? SignalState<VexClaimSummary>.NotQueried(),
|
|
Reachability = reachability ?? SignalState<ReachabilityEvidence>.NotQueried(),
|
|
Runtime = runtime ?? SignalState<RuntimeEvidence>.NotQueried(),
|
|
Backport = SignalState<BackportEvidence>.NotQueried(),
|
|
Sbom = SignalState<SbomLineageEvidence>.NotQueried(),
|
|
Cvss = SignalState<CvssEvidence>.NotQueried(),
|
|
SnapshotAt = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
return new DeterminizationContext
|
|
{
|
|
SignalSnapshot = snapshot,
|
|
UncertaintyScore = new UncertaintyScore
|
|
{
|
|
Entropy = entropy,
|
|
Tier = tier,
|
|
Completeness = 1.0 - entropy,
|
|
MissingSignals = []
|
|
},
|
|
Decay = new ObservationDecay
|
|
{
|
|
LastSignalUpdate = DateTimeOffset.UtcNow.AddDays(-1),
|
|
AgeDays = 1,
|
|
DecayedMultiplier = isStale ? 0.3 : 0.9,
|
|
IsStale = isStale
|
|
},
|
|
TrustScore = trustScore,
|
|
Environment = environment
|
|
};
|
|
}
|
|
}
|