update evidence bundle to include new evidence types and implement ProofSpine integration
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-15 09:15:30 +02:00
parent 8c8f0c632d
commit 505fe7a885
49 changed files with 4756 additions and 551 deletions

View File

@@ -0,0 +1,27 @@
namespace StellaOps.Policy.Suppression;
using System.Collections.Immutable;
/// <summary>
/// Provider for checking policy suppression overrides (waivers).
/// </summary>
public interface ISuppressionOverrideProvider
{
bool HasActiveOverride(FindingKey findingKey);
}
/// <summary>
/// Simple in-memory override provider for tests and local runs.
/// </summary>
public sealed class InMemorySuppressionOverrideProvider : ISuppressionOverrideProvider
{
private readonly ImmutableHashSet<FindingKey> _overrides;
public InMemorySuppressionOverrideProvider(IEnumerable<FindingKey>? overrides = null)
{
_overrides = overrides?.ToImmutableHashSet() ?? ImmutableHashSet<FindingKey>.Empty;
}
public bool HasActiveOverride(FindingKey findingKey) => _overrides.Contains(findingKey);
}

View File

@@ -0,0 +1,190 @@
namespace StellaOps.Policy.Suppression;
using System.Collections.Immutable;
/// <summary>
/// Evaluates whether a finding should be suppressed based on the 4-condition rule.
/// All conditions must be met for suppression:
/// 1. reachable == false
/// 2. vex_status == NOT_AFFECTED
/// 3. kev == false
/// 4. No policy override active
/// </summary>
public sealed class SuppressionRuleEvaluator
{
private readonly ISuppressionOverrideProvider _overrideProvider;
public SuppressionRuleEvaluator(ISuppressionOverrideProvider overrideProvider)
{
_overrideProvider = overrideProvider;
}
/// <summary>
/// Evaluates suppression for a single finding.
/// </summary>
public SuppressionResult Evaluate(SuppressionInput input)
{
var conditions = new List<SuppressionConditionResult>
{
EvaluateReachableCondition(input),
EvaluateVexCondition(input),
EvaluateKevCondition(input),
EvaluateOverrideCondition(input),
};
var shouldSuppress = conditions.All(c => c.Passed);
return new SuppressionResult(
FindingKey: input.FindingKey,
Suppressed: shouldSuppress,
Conditions: conditions.ToImmutableArray(),
Reason: shouldSuppress
? "All 4 suppression conditions met"
: $"Condition failed: {conditions.First(c => !c.Passed).ConditionName}");
}
/// <summary>
/// Evaluates suppression for multiple findings (batch).
/// </summary>
public ImmutableArray<SuppressionResult> EvaluateBatch(IEnumerable<SuppressionInput> inputs)
{
ArgumentNullException.ThrowIfNull(inputs);
return inputs.Select(Evaluate).ToImmutableArray();
}
/// <summary>
/// Evaluates patch churn suppression: version changes with no material risk change.
/// </summary>
public SuppressionResult EvaluatePatchChurn(PatchChurnInput input)
{
var conditions = new List<SuppressionConditionResult>
{
new(
ConditionName: "version_changed",
Passed: input.VersionChanged,
Reason: input.VersionChanged ? "Version changed" : "Version unchanged"),
new(
ConditionName: "not_in_affected_range",
Passed: !input.WasInAffectedRange && !input.IsInAffectedRange,
Reason: $"Was: {input.WasInAffectedRange}, Now: {input.IsInAffectedRange}"),
new(
ConditionName: "no_kev",
Passed: !input.Kev,
Reason: input.Kev ? "KEV flagged" : "Not KEV"),
new(
ConditionName: "no_policy_flip",
Passed: !input.PolicyFlipped,
Reason: input.PolicyFlipped ? "Policy changed" : "Policy unchanged"),
};
var shouldSuppress = conditions.All(c => c.Passed);
return new SuppressionResult(
FindingKey: input.FindingKey,
Suppressed: shouldSuppress,
Conditions: conditions.ToImmutableArray(),
Reason: shouldSuppress ? "Patch churn - no material change" : "Material change detected");
}
private SuppressionConditionResult EvaluateReachableCondition(SuppressionInput input)
{
var passed = input.Reachable == false;
return new SuppressionConditionResult(
ConditionName: "unreachable",
Passed: passed,
Reason: input.Reachable switch
{
null => "Reachability unknown",
true => "Code is reachable",
false => "Code is unreachable"
});
}
private static SuppressionConditionResult EvaluateVexCondition(SuppressionInput input)
{
var passed = input.VexStatus == VexStatus.NotAffected;
return new SuppressionConditionResult(
ConditionName: "vex_not_affected",
Passed: passed,
Reason: $"VEX status: {input.VexStatus}");
}
private static SuppressionConditionResult EvaluateKevCondition(SuppressionInput input)
{
var passed = !input.Kev;
return new SuppressionConditionResult(
ConditionName: "not_kev",
Passed: passed,
Reason: input.Kev ? "Known Exploited Vulnerability" : "Not in KEV catalog");
}
private SuppressionConditionResult EvaluateOverrideCondition(SuppressionInput input)
{
var hasOverride = _overrideProvider.HasActiveOverride(input.FindingKey);
return new SuppressionConditionResult(
ConditionName: "no_override",
Passed: !hasOverride,
Reason: hasOverride ? "Policy override active" : "No policy override");
}
}
/// <summary>
/// Input for suppression evaluation.
/// </summary>
public sealed record SuppressionInput(
FindingKey FindingKey,
bool? Reachable,
VexStatus VexStatus,
bool Kev);
/// <summary>
/// Input for patch churn suppression evaluation.
/// </summary>
public sealed record PatchChurnInput(
FindingKey FindingKey,
bool VersionChanged,
bool WasInAffectedRange,
bool IsInAffectedRange,
bool Kev,
bool PolicyFlipped);
/// <summary>
/// Result of suppression evaluation.
/// </summary>
public sealed record SuppressionResult(
FindingKey FindingKey,
bool Suppressed,
ImmutableArray<SuppressionConditionResult> Conditions,
string Reason);
/// <summary>
/// Result of a single suppression condition.
/// </summary>
public sealed record SuppressionConditionResult(
string ConditionName,
bool Passed,
string Reason);
/// <summary>
/// Unique identifier for a vulnerability finding.
/// </summary>
public sealed record FindingKey(
string ComponentPurl,
string ComponentVersion,
string CveId)
{
public override string ToString() => $"{ComponentPurl}@{ComponentVersion}:{CveId}";
}
/// <summary>
/// VEX status values.
/// </summary>
public enum VexStatus
{
Unknown,
Affected,
NotAffected,
Fixed,
UnderInvestigation
}

View File

@@ -0,0 +1,145 @@
using StellaOps.Policy.Suppression;
using Xunit;
namespace StellaOps.Policy.Tests.Suppression;
public sealed class SuppressionRuleEvaluatorTests
{
[Fact]
public void Evaluate_Suppresses_WhenAllConditionsPass()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: false,
VexStatus: VexStatus.NotAffected,
Kev: false));
Assert.True(result.Suppressed);
Assert.All(result.Conditions, condition => Assert.True(condition.Passed, condition.ConditionName));
Assert.Equal("All 4 suppression conditions met", result.Reason);
}
[Fact]
public void Evaluate_DoesNotSuppress_WhenReachableIsTrue()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: true,
VexStatus: VexStatus.NotAffected,
Kev: false));
Assert.False(result.Suppressed);
Assert.Contains("unreachable", result.Reason, StringComparison.Ordinal);
}
[Fact]
public void Evaluate_DoesNotSuppress_WhenReachableIsUnknown()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: null,
VexStatus: VexStatus.NotAffected,
Kev: false));
Assert.False(result.Suppressed);
Assert.Contains("unreachable", result.Reason, StringComparison.Ordinal);
}
[Fact]
public void Evaluate_DoesNotSuppress_WhenVexIsNotNotAffected()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: false,
VexStatus: VexStatus.Affected,
Kev: false));
Assert.False(result.Suppressed);
Assert.Contains("vex_not_affected", result.Reason, StringComparison.Ordinal);
}
[Fact]
public void Evaluate_DoesNotSuppress_WhenKev()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: false,
VexStatus: VexStatus.NotAffected,
Kev: true));
Assert.False(result.Suppressed);
Assert.Contains("not_kev", result.Reason, StringComparison.Ordinal);
}
[Fact]
public void Evaluate_DoesNotSuppress_WhenOverrideActive()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider(new[] { key }));
var result = evaluator.Evaluate(new SuppressionInput(
FindingKey: key,
Reachable: false,
VexStatus: VexStatus.NotAffected,
Kev: false));
Assert.False(result.Suppressed);
Assert.Contains("no_override", result.Reason, StringComparison.Ordinal);
}
[Fact]
public void EvaluatePatchChurn_Suppresses_WhenVersionChangesButNoMaterialChange()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.EvaluatePatchChurn(new PatchChurnInput(
FindingKey: key,
VersionChanged: true,
WasInAffectedRange: false,
IsInAffectedRange: false,
Kev: false,
PolicyFlipped: false));
Assert.True(result.Suppressed);
Assert.Equal("Patch churn - no material change", result.Reason);
}
[Fact]
public void EvaluatePatchChurn_DoesNotSuppress_WhenInAffectedRange()
{
var key = CreateFindingKey();
var evaluator = new SuppressionRuleEvaluator(new InMemorySuppressionOverrideProvider());
var result = evaluator.EvaluatePatchChurn(new PatchChurnInput(
FindingKey: key,
VersionChanged: true,
WasInAffectedRange: false,
IsInAffectedRange: true,
Kev: false,
PolicyFlipped: false));
Assert.False(result.Suppressed);
}
private static FindingKey CreateFindingKey() => new(
ComponentPurl: "pkg:nuget/Example.Component@1.0.0",
ComponentVersion: "1.0.0",
CveId: "CVE-2025-0001");
}