new advisories work and features gaps work
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) 2025 StellaOps
|
||||
// Sprint: SPRINT_20260112_004_LB_attested_reduction_scoring (EWS-ATT-005)
|
||||
// Description: Tests for attested-reduction scoring path
|
||||
|
||||
using StellaOps.Signals.EvidenceWeightedScore;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Signals.Tests.EvidenceWeightedScore;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class AttestedReductionScoringTests
|
||||
{
|
||||
private readonly EvidenceWeightedScoreCalculator _calculator;
|
||||
private readonly EvidenceWeightPolicy _policy;
|
||||
|
||||
public AttestedReductionScoringTests()
|
||||
{
|
||||
_calculator = new EvidenceWeightedScoreCalculator(TimeProvider.System);
|
||||
_policy = new EvidenceWeightPolicy
|
||||
{
|
||||
Version = "ews.v1",
|
||||
Profile = "test",
|
||||
Weights = EvidenceWeights.Default,
|
||||
AttestedReduction = AttestedReductionConfig.EnabledDefault
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithAttestedReductionDisabled_UsesStandardPath()
|
||||
{
|
||||
var policy = _policy with { AttestedReduction = AttestedReductionConfig.Default };
|
||||
var input = CreateInput(xpl: 0.5);
|
||||
|
||||
var result = _calculator.Calculate(input, policy);
|
||||
|
||||
Assert.DoesNotContain("attested-reduction", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithAttestedReductionEnabled_UsesAttestedPath()
|
||||
{
|
||||
var input = CreateInput(xpl: 0.5);
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Contains("attested-reduction", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AnchoredVexNotAffected_ReturnsZeroScore()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.8,
|
||||
vexStatus: "not_affected",
|
||||
vexAnchor: AnchorMetadata.CreateAnchored("sha256:abc", "vex/v1"));
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Equal(0, result.Score);
|
||||
Assert.Equal(ScoreBucket.Watchlist, result.Bucket);
|
||||
Assert.Contains("anchored-vex", result.Flags);
|
||||
Assert.Contains("vendor-na", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AnchoredVexFixed_ReturnsZeroScore()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.9,
|
||||
vexStatus: "fixed",
|
||||
vexAnchor: AnchorMetadata.CreateAnchored("sha256:def", "vex/v1"));
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Equal(0, result.Score);
|
||||
Assert.Contains("anchored-vex", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AnchoredAffectedWithRuntime_HardFails()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: "affected",
|
||||
vexAnchor: AnchorMetadata.CreateAnchored("sha256:ghi", "vex/v1"),
|
||||
runtimeDetails: new RuntimeInput
|
||||
{
|
||||
Posture = RuntimePosture.EbpfDeep,
|
||||
ObservationCount = 10,
|
||||
RecencyFactor = 0.9,
|
||||
DirectPathObserved = true,
|
||||
Anchor = AnchorMetadata.CreateAnchored("sha256:jkl", "runtime/v1")
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Equal(100, result.Score); // Hard fail = 1.0 * 100
|
||||
Assert.Equal(ScoreBucket.ActNow, result.Bucket);
|
||||
Assert.Contains("hard-fail", result.Flags);
|
||||
Assert.Contains("anchored-vex", result.Flags);
|
||||
Assert.Contains("anchored-runtime", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_UnanchoredVexNotAffected_DoesNotShortCircuit()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: "not_affected",
|
||||
vexAnchor: null); // No anchor
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
// Should not be 0 because VEX is not anchored
|
||||
Assert.NotEqual(0, result.Score);
|
||||
Assert.DoesNotContain("anchored-vex", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AnchoredReachabilityNotReachable_AppliesBonus()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
reachabilityDetails: new ReachabilityInput
|
||||
{
|
||||
State = ReachabilityState.NotReachable,
|
||||
Confidence = 0.9,
|
||||
Anchor = AnchorMetadata.CreateAnchored("sha256:mno", "reachability/v1")
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Contains("anchored-reachability", result.Flags);
|
||||
// Score should be affected by reachability bonus
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AnchoredBackportFixed_AppliesReduction()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
backportDetails: new BackportInput
|
||||
{
|
||||
EvidenceTier = BackportEvidenceTier.SignedProof,
|
||||
Status = BackportStatus.Fixed,
|
||||
Confidence = 0.95,
|
||||
Anchor = AnchorMetadata.CreateAnchored("sha256:pqr", "backport/v1")
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Contains("anchored-backport", result.Flags);
|
||||
// Score should be reduced by patch proof reduction
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_WithAnchoredEvidence_ReducesEpssInfluence()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.8,
|
||||
backportDetails: new BackportInput
|
||||
{
|
||||
EvidenceTier = BackportEvidenceTier.SignedProof,
|
||||
Status = BackportStatus.NotAffected,
|
||||
Confidence = 0.9,
|
||||
Anchor = AnchorMetadata.CreateAnchored("sha256:stu", "backport/v1")
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Contains("epss-reduced", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_PolicyDigest_IncludesAttestedReductionConfig()
|
||||
{
|
||||
var policy1 = _policy;
|
||||
var policy2 = _policy with
|
||||
{
|
||||
AttestedReduction = _policy.AttestedReduction with { ReachabilityBonus = 0.5 }
|
||||
};
|
||||
|
||||
var input = CreateInput(xpl: 0.5);
|
||||
|
||||
var result1 = _calculator.Calculate(input, policy1);
|
||||
var result2 = _calculator.Calculate(input, policy2);
|
||||
|
||||
// Different attested-reduction config should produce different digests
|
||||
Assert.NotEqual(result1.PolicyDigest, result2.PolicyDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_AttestedReduction_IsDeterministic()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: "affected",
|
||||
backportDetails: new BackportInput
|
||||
{
|
||||
EvidenceTier = BackportEvidenceTier.VendorVex,
|
||||
Status = BackportStatus.NotAffected,
|
||||
Confidence = 0.8,
|
||||
Anchor = AnchorMetadata.CreateAnchored("sha256:xyz", "backport/v1")
|
||||
});
|
||||
|
||||
var result1 = _calculator.Calculate(input, _policy);
|
||||
var result2 = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Equal(result1.Score, result2.Score);
|
||||
Assert.Equal(result1.Bucket, result2.Bucket);
|
||||
Assert.Equal(result1.PolicyDigest, result2.PolicyDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_UnverifiedAnchor_DoesNotTriggerPrecedence()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: "not_affected",
|
||||
vexAnchor: new AnchorMetadata
|
||||
{
|
||||
IsAnchored = true,
|
||||
DsseEnvelopeDigest = "sha256:abc",
|
||||
PredicateType = "vex/v1",
|
||||
VerificationStatus = AnchorVerificationStatus.Unverified // Not verified
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
// Should not short-circuit because anchor is unverified
|
||||
Assert.NotEqual(0, result.Score);
|
||||
Assert.DoesNotContain("anchored-vex", result.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_VerifiedAnchor_TriggersPrecedence()
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: "not_affected",
|
||||
vexAnchor: new AnchorMetadata
|
||||
{
|
||||
IsAnchored = true,
|
||||
DsseEnvelopeDigest = "sha256:abc",
|
||||
PredicateType = "vex/v1",
|
||||
VerificationStatus = AnchorVerificationStatus.Verified
|
||||
});
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
Assert.Equal(0, result.Score);
|
||||
Assert.Contains("anchored-vex", result.Flags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("not_affected", 0)]
|
||||
[InlineData("fixed", 0)]
|
||||
[InlineData("under_investigation", -1)] // -1 means not short-circuited
|
||||
[InlineData("affected", -1)]
|
||||
public void Calculate_VexStatusPrecedence_ReturnsExpectedScore(string vexStatus, int expectedScore)
|
||||
{
|
||||
var input = CreateInput(
|
||||
xpl: 0.5,
|
||||
vexStatus: vexStatus,
|
||||
vexAnchor: AnchorMetadata.CreateAnchored("sha256:test", "vex/v1"));
|
||||
|
||||
var result = _calculator.Calculate(input, _policy);
|
||||
|
||||
if (expectedScore >= 0)
|
||||
{
|
||||
Assert.Equal(expectedScore, result.Score);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not short-circuited, should have some score
|
||||
Assert.True(result.Score > 0 || result.Flags.Contains("hard-fail"));
|
||||
}
|
||||
}
|
||||
|
||||
private static EvidenceWeightedScoreInput CreateInput(
|
||||
double xpl = 0.0,
|
||||
double rch = 0.0,
|
||||
double rts = 0.0,
|
||||
double bkp = 0.0,
|
||||
double src = 0.5,
|
||||
double mit = 0.0,
|
||||
string? vexStatus = null,
|
||||
AnchorMetadata? vexAnchor = null,
|
||||
ReachabilityInput? reachabilityDetails = null,
|
||||
RuntimeInput? runtimeDetails = null,
|
||||
BackportInput? backportDetails = null)
|
||||
{
|
||||
return new EvidenceWeightedScoreInput
|
||||
{
|
||||
FindingId = "CVE-2024-1234@pkg:test/lib@1.0.0",
|
||||
Xpl = xpl,
|
||||
Rch = rch,
|
||||
Rts = rts,
|
||||
Bkp = bkp,
|
||||
Src = src,
|
||||
Mit = mit,
|
||||
VexStatus = vexStatus,
|
||||
VexAnchor = vexAnchor,
|
||||
ReachabilityDetails = reachabilityDetails,
|
||||
RuntimeDetails = runtimeDetails,
|
||||
BackportDetails = backportDetails
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user