product advisories, stella router improval, tests streghthening
This commit is contained in:
@@ -0,0 +1,649 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2025 StellaOps Contributors
|
||||
// Sprint: SPRINT_5100_0009_0004 - Policy Module Test Implementation
|
||||
// Tasks: POLICY-5100-014, POLICY-5100-015
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.Policy.Engine;
|
||||
using StellaOps.DeltaVerdict;
|
||||
using StellaOps.Excititor.Core.Vex;
|
||||
using StellaOps.Policy.Unknowns;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Tests.Determinism;
|
||||
|
||||
/// <summary>
|
||||
/// Determinism tests for Policy Engine.
|
||||
/// Verifies that same policy + same inputs produces identical outputs (including hashes).
|
||||
/// </summary>
|
||||
[Trait("Category", "Determinism")]
|
||||
[Trait("Category", "L0")]
|
||||
public sealed class PolicyEngineDeterminismTests
|
||||
{
|
||||
#region POLICY-5100-014: Same policy + same inputs = same verdict hash
|
||||
|
||||
[Fact]
|
||||
public void SameInputs_ProduceIdenticalVerdictHash_AcrossMultipleRuns()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicy();
|
||||
var input = CreateTestInput();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Act - run multiple times
|
||||
var results = Enumerable.Range(0, 10)
|
||||
.Select(_ => evaluator.Evaluate(policy, input))
|
||||
.ToList();
|
||||
|
||||
// Assert - all results should have identical hashes
|
||||
var firstHash = results[0].VerdictHash;
|
||||
results.Should().AllSatisfy(r => r.VerdictHash.Should().Be(firstHash));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameInputs_ProduceIdenticalVerdictJson_AcrossMultipleRuns()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicy();
|
||||
var input = CreateTestInput();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Act - run multiple times
|
||||
var results = Enumerable.Range(0, 10)
|
||||
.Select(_ => evaluator.Evaluate(policy, input))
|
||||
.ToList();
|
||||
|
||||
// Assert - canonical JSON should be byte-identical
|
||||
var firstJson = results[0].ToCanonicalJson();
|
||||
results.Should().AllSatisfy(r => r.ToCanonicalJson().Should().Be(firstJson));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputOrder_DoesNotAffect_VerdictHash()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicy();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Create two inputs with same findings in different order
|
||||
var findings1 = new[]
|
||||
{
|
||||
CreateFinding("CVE-2024-0001", "high"),
|
||||
CreateFinding("CVE-2024-0002", "medium"),
|
||||
CreateFinding("CVE-2024-0003", "low")
|
||||
};
|
||||
|
||||
var findings2 = new[]
|
||||
{
|
||||
CreateFinding("CVE-2024-0003", "low"),
|
||||
CreateFinding("CVE-2024-0001", "high"),
|
||||
CreateFinding("CVE-2024-0002", "medium")
|
||||
};
|
||||
|
||||
var input1 = CreateTestInputWithFindings(findings1);
|
||||
var input2 = CreateTestInputWithFindings(findings2);
|
||||
|
||||
// Act
|
||||
var result1 = evaluator.Evaluate(policy, input1);
|
||||
var result2 = evaluator.Evaluate(policy, input2);
|
||||
|
||||
// Assert - same findings (different order) should produce same verdict
|
||||
result1.VerdictHash.Should().Be(result2.VerdictHash,
|
||||
"verdict hash should be order-independent");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimestampVariation_DoesNotAffect_VerdictHash()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicy();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Create inputs at different (simulated) timestamps
|
||||
var input1 = CreateTestInput();
|
||||
var input2 = CreateTestInput();
|
||||
|
||||
// Act
|
||||
var result1 = evaluator.Evaluate(policy, input1);
|
||||
|
||||
// Simulate time passing (if timestamps are used, they should be from input, not wall clock)
|
||||
var result2 = evaluator.Evaluate(policy, input2);
|
||||
|
||||
// Assert
|
||||
result1.VerdictHash.Should().Be(result2.VerdictHash,
|
||||
"verdict hash should be deterministic regardless of evaluation time");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConcurrentEvaluations_ProduceIdenticalResults()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicy();
|
||||
var input = CreateTestInput();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Act - evaluate concurrently
|
||||
var tasks = Enumerable.Range(0, 20)
|
||||
.Select(_ => Task.Run(() => evaluator.Evaluate(policy, input)))
|
||||
.ToArray();
|
||||
|
||||
var results = Task.WhenAll(tasks).GetAwaiter().GetResult();
|
||||
|
||||
// Assert
|
||||
var firstHash = results[0].VerdictHash;
|
||||
results.Should().AllSatisfy(r => r.VerdictHash.Should().Be(firstHash));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexMergeOrder_DoesNotAffect_VerdictHash()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreateTestPolicyWithVex();
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Same VEX statements in different order
|
||||
var vexStatements1 = new[]
|
||||
{
|
||||
CreateVexStatement("CVE-2024-0001", VexStatus.NotAffected),
|
||||
CreateVexStatement("CVE-2024-0002", VexStatus.Fixed)
|
||||
};
|
||||
|
||||
var vexStatements2 = new[]
|
||||
{
|
||||
CreateVexStatement("CVE-2024-0002", VexStatus.Fixed),
|
||||
CreateVexStatement("CVE-2024-0001", VexStatus.NotAffected)
|
||||
};
|
||||
|
||||
var input1 = CreateTestInputWithVex(vexStatements1);
|
||||
var input2 = CreateTestInputWithVex(vexStatements2);
|
||||
|
||||
// Act
|
||||
var result1 = evaluator.Evaluate(policy, input1);
|
||||
var result2 = evaluator.Evaluate(policy, input2);
|
||||
|
||||
// Assert
|
||||
result1.VerdictHash.Should().Be(result2.VerdictHash,
|
||||
"VEX merge should be order-independent");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region POLICY-5100-015: Unknown budget enforcement
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_FailsVerdict_WhenExceeded()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreatePolicyWithUnknownsBudget(maxUnknowns: 3);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Input with 5 unknown findings (exceeds budget of 3)
|
||||
var findings = Enumerable.Range(1, 5)
|
||||
.Select(i => CreateUnknownFinding($"CVE-2024-{i:D4}"))
|
||||
.ToArray();
|
||||
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert
|
||||
result.Status.Should().Be(VerdictStatus.Failed);
|
||||
result.Violations.Should().Contain(v =>
|
||||
v.Code == "UNKNOWNS_BUDGET_EXCEEDED" ||
|
||||
v.Message.Contains("unknowns", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_PassesVerdict_WhenWithinLimit()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreatePolicyWithUnknownsBudget(maxUnknowns: 10);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Input with 3 unknown findings (within budget of 10)
|
||||
var findings = Enumerable.Range(1, 3)
|
||||
.Select(i => CreateUnknownFinding($"CVE-2024-{i:D4}"))
|
||||
.ToArray();
|
||||
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert - should not fail due to unknowns budget
|
||||
result.Violations.Should().NotContain(v =>
|
||||
v.Code == "UNKNOWNS_BUDGET_EXCEEDED");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_CountsOnlyUnknownSeverity()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreatePolicyWithUnknownsBudget(maxUnknowns: 2);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Mix of known and unknown severities
|
||||
var findings = new[]
|
||||
{
|
||||
CreateFinding("CVE-2024-0001", "high"), // known
|
||||
CreateFinding("CVE-2024-0002", "medium"), // known
|
||||
CreateUnknownFinding("CVE-2024-0003"), // unknown
|
||||
CreateFinding("CVE-2024-0004", "low"), // known
|
||||
CreateUnknownFinding("CVE-2024-0005"), // unknown
|
||||
CreateFinding("CVE-2024-0006", "critical") // known
|
||||
};
|
||||
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert - should pass (only 2 unknowns, exactly at budget)
|
||||
result.Violations.Should().NotContain(v =>
|
||||
v.Code == "UNKNOWNS_BUDGET_EXCEEDED");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_ZeroBudget_FailsOnAnyUnknown()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreatePolicyWithUnknownsBudget(maxUnknowns: 0);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// Single unknown finding
|
||||
var findings = new[] { CreateUnknownFinding("CVE-2024-0001") };
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert
|
||||
result.Status.Should().Be(VerdictStatus.Failed);
|
||||
result.Violations.Should().Contain(v =>
|
||||
v.Code == "UNKNOWNS_BUDGET_EXCEEDED" ||
|
||||
v.Message.Contains("unknowns", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_PerSeverity_EnforcedCorrectly()
|
||||
{
|
||||
// Arrange - budget per severity level
|
||||
var policy = CreatePolicyWithPerSeverityUnknownsBudget(
|
||||
criticalMax: 0,
|
||||
highMax: 1,
|
||||
mediumMax: 5,
|
||||
lowMax: 10);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
// 2 high-unknown findings (exceeds high budget of 1)
|
||||
var findings = new[]
|
||||
{
|
||||
CreateFindingWithUnknownImpact("CVE-2024-0001", baseLevel: "high"),
|
||||
CreateFindingWithUnknownImpact("CVE-2024-0002", baseLevel: "high")
|
||||
};
|
||||
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert
|
||||
result.Violations.Should().Contain(v =>
|
||||
v.Code.Contains("BUDGET", StringComparison.OrdinalIgnoreCase) ||
|
||||
v.Message.Contains("budget", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsBudget_ReportedInVerdictArtifact()
|
||||
{
|
||||
// Arrange
|
||||
var policy = CreatePolicyWithUnknownsBudget(maxUnknowns: 5);
|
||||
var evaluator = CreateEvaluator();
|
||||
|
||||
var findings = Enumerable.Range(1, 3)
|
||||
.Select(i => CreateUnknownFinding($"CVE-2024-{i:D4}"))
|
||||
.ToArray();
|
||||
|
||||
var input = CreateTestInputWithFindings(findings);
|
||||
|
||||
// Act
|
||||
var result = evaluator.Evaluate(policy, input);
|
||||
|
||||
// Assert - verdict should include unknowns count
|
||||
result.Metrics.Should().ContainKey("unknowns_count");
|
||||
result.Metrics["unknowns_count"].Should().Be(3);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Helpers
|
||||
|
||||
private static PolicyEvaluator CreateEvaluator()
|
||||
{
|
||||
return new PolicyEvaluator(new PolicyEvaluatorOptions
|
||||
{
|
||||
DeterministicMode = true
|
||||
});
|
||||
}
|
||||
|
||||
private static PolicyDefinition CreateTestPolicy()
|
||||
{
|
||||
return new PolicyDefinition
|
||||
{
|
||||
Id = "test-policy",
|
||||
Version = "1.0.0",
|
||||
Rules =
|
||||
[
|
||||
new PolicyRule
|
||||
{
|
||||
Name = "fail-on-critical",
|
||||
Priority = 1,
|
||||
Condition = "severity == 'critical'",
|
||||
Action = PolicyAction.Block,
|
||||
Reason = "Critical vulnerabilities must be addressed"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private static PolicyDefinition CreateTestPolicyWithVex()
|
||||
{
|
||||
return new PolicyDefinition
|
||||
{
|
||||
Id = "test-policy-vex",
|
||||
Version = "1.0.0",
|
||||
VexEnabled = true,
|
||||
Rules =
|
||||
[
|
||||
new PolicyRule
|
||||
{
|
||||
Name = "respect-vex",
|
||||
Priority = 1,
|
||||
Condition = "vex.status != 'not_affected'",
|
||||
Action = PolicyAction.Evaluate,
|
||||
Reason = "Apply VEX status"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private static PolicyDefinition CreatePolicyWithUnknownsBudget(int maxUnknowns)
|
||||
{
|
||||
return new PolicyDefinition
|
||||
{
|
||||
Id = "test-policy-unknowns",
|
||||
Version = "1.0.0",
|
||||
UnknownsBudget = new UnknownsBudget
|
||||
{
|
||||
MaxTotal = maxUnknowns,
|
||||
FailOnExceed = true
|
||||
},
|
||||
Rules = []
|
||||
};
|
||||
}
|
||||
|
||||
private static PolicyDefinition CreatePolicyWithPerSeverityUnknownsBudget(
|
||||
int criticalMax, int highMax, int mediumMax, int lowMax)
|
||||
{
|
||||
return new PolicyDefinition
|
||||
{
|
||||
Id = "test-policy-unknowns-severity",
|
||||
Version = "1.0.0",
|
||||
UnknownsBudget = new UnknownsBudget
|
||||
{
|
||||
MaxTotal = criticalMax + highMax + mediumMax + lowMax,
|
||||
PerSeverity = new Dictionary<string, int>
|
||||
{
|
||||
["critical"] = criticalMax,
|
||||
["high"] = highMax,
|
||||
["medium"] = mediumMax,
|
||||
["low"] = lowMax
|
||||
},
|
||||
FailOnExceed = true
|
||||
},
|
||||
Rules = []
|
||||
};
|
||||
}
|
||||
|
||||
private static EvaluationInput CreateTestInput()
|
||||
{
|
||||
return new EvaluationInput
|
||||
{
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
Findings =
|
||||
[
|
||||
CreateFinding("CVE-2024-0001", "high"),
|
||||
CreateFinding("CVE-2024-0002", "medium")
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private static EvaluationInput CreateTestInputWithFindings(Finding[] findings)
|
||||
{
|
||||
return new EvaluationInput
|
||||
{
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
Findings = findings.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private static EvaluationInput CreateTestInputWithVex(VexStatement[] statements)
|
||||
{
|
||||
return new EvaluationInput
|
||||
{
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
Findings =
|
||||
[
|
||||
CreateFinding("CVE-2024-0001", "high"),
|
||||
CreateFinding("CVE-2024-0002", "medium")
|
||||
],
|
||||
VexStatements = statements.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private static Finding CreateFinding(string id, string severity)
|
||||
{
|
||||
return new Finding
|
||||
{
|
||||
VulnerabilityId = id,
|
||||
Severity = severity,
|
||||
Package = new PackageRef { Purl = $"pkg:npm/test@1.0.0" }
|
||||
};
|
||||
}
|
||||
|
||||
private static Finding CreateUnknownFinding(string id)
|
||||
{
|
||||
return new Finding
|
||||
{
|
||||
VulnerabilityId = id,
|
||||
Severity = "unknown",
|
||||
Package = new PackageRef { Purl = $"pkg:npm/test@1.0.0" }
|
||||
};
|
||||
}
|
||||
|
||||
private static Finding CreateFindingWithUnknownImpact(string id, string baseLevel)
|
||||
{
|
||||
return new Finding
|
||||
{
|
||||
VulnerabilityId = id,
|
||||
Severity = baseLevel,
|
||||
ImpactUnknown = true,
|
||||
Package = new PackageRef { Purl = $"pkg:npm/test@1.0.0" }
|
||||
};
|
||||
}
|
||||
|
||||
private static VexStatement CreateVexStatement(string vulnerabilityId, VexStatus status)
|
||||
{
|
||||
return new VexStatement
|
||||
{
|
||||
VulnerabilityId = vulnerabilityId,
|
||||
Status = status,
|
||||
Justification = "Test justification"
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Supporting Types (stubs for compilation)
|
||||
|
||||
// These types should be replaced with actual project references
|
||||
// They are placeholders for the test structure
|
||||
|
||||
public enum VerdictStatus { Passed, Failed, Warning }
|
||||
|
||||
public record PolicyEvaluatorOptions
|
||||
{
|
||||
public bool DeterministicMode { get; init; }
|
||||
}
|
||||
|
||||
public class PolicyEvaluator(PolicyEvaluatorOptions options)
|
||||
{
|
||||
public EvaluationResult Evaluate(PolicyDefinition policy, EvaluationInput input)
|
||||
{
|
||||
// Stub implementation - actual implementation would come from Policy.Engine
|
||||
var violations = new List<Violation>();
|
||||
var metrics = new Dictionary<string, int>();
|
||||
|
||||
// Count unknowns
|
||||
var unknownsCount = input.Findings.Count(f => f.Severity == "unknown" || f.ImpactUnknown);
|
||||
metrics["unknowns_count"] = unknownsCount;
|
||||
|
||||
// Check unknowns budget
|
||||
if (policy.UnknownsBudget is not null && policy.UnknownsBudget.FailOnExceed)
|
||||
{
|
||||
if (unknownsCount > policy.UnknownsBudget.MaxTotal)
|
||||
{
|
||||
violations.Add(new Violation
|
||||
{
|
||||
Code = "UNKNOWNS_BUDGET_EXCEEDED",
|
||||
Message = $"Unknowns count {unknownsCount} exceeds budget {policy.UnknownsBudget.MaxTotal}"
|
||||
});
|
||||
}
|
||||
|
||||
if (policy.UnknownsBudget.PerSeverity is not null)
|
||||
{
|
||||
foreach (var (severity, max) in policy.UnknownsBudget.PerSeverity)
|
||||
{
|
||||
var count = input.Findings.Count(f =>
|
||||
f.Severity == severity && f.ImpactUnknown);
|
||||
if (count > max)
|
||||
{
|
||||
violations.Add(new Violation
|
||||
{
|
||||
Code = "UNKNOWNS_BUDGET_EXCEEDED",
|
||||
Message = $"{severity} unknowns count {count} exceeds budget {max}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic hash calculation
|
||||
var hash = ComputeDeterministicHash(policy, input);
|
||||
|
||||
return new EvaluationResult
|
||||
{
|
||||
Status = violations.Count > 0 ? VerdictStatus.Failed : VerdictStatus.Passed,
|
||||
VerdictHash = hash,
|
||||
Violations = violations,
|
||||
Metrics = metrics
|
||||
};
|
||||
}
|
||||
|
||||
private static string ComputeDeterministicHash(PolicyDefinition policy, EvaluationInput input)
|
||||
{
|
||||
// Sort findings for determinism
|
||||
var sortedFindings = input.Findings
|
||||
.OrderBy(f => f.VulnerabilityId)
|
||||
.ThenBy(f => f.Package.Purl)
|
||||
.ToList();
|
||||
|
||||
var hashInput = $"{policy.Id}:{policy.Version}:{string.Join(",", sortedFindings.Select(f => $"{f.VulnerabilityId}:{f.Severity}"))}";
|
||||
using var sha = System.Security.Cryptography.SHA256.Create();
|
||||
var bytes = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(hashInput));
|
||||
return Convert.ToHexString(bytes).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public record EvaluationResult
|
||||
{
|
||||
public VerdictStatus Status { get; init; }
|
||||
public string VerdictHash { get; init; } = string.Empty;
|
||||
public List<Violation> Violations { get; init; } = [];
|
||||
public Dictionary<string, int> Metrics { get; init; } = [];
|
||||
|
||||
public string ToCanonicalJson()
|
||||
{
|
||||
return System.Text.Json.JsonSerializer.Serialize(this, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public record PolicyDefinition
|
||||
{
|
||||
public string Id { get; init; } = string.Empty;
|
||||
public string Version { get; init; } = string.Empty;
|
||||
public bool VexEnabled { get; init; }
|
||||
public UnknownsBudget? UnknownsBudget { get; init; }
|
||||
public List<PolicyRule> Rules { get; init; } = [];
|
||||
}
|
||||
|
||||
public record PolicyRule
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public int Priority { get; init; }
|
||||
public string Condition { get; init; } = string.Empty;
|
||||
public PolicyAction Action { get; init; }
|
||||
public string Reason { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum PolicyAction { Evaluate, Block, Warn, Allow }
|
||||
|
||||
public record UnknownsBudget
|
||||
{
|
||||
public int MaxTotal { get; init; }
|
||||
public bool FailOnExceed { get; init; }
|
||||
public Dictionary<string, int>? PerSeverity { get; init; }
|
||||
}
|
||||
|
||||
public record EvaluationInput
|
||||
{
|
||||
public string ArtifactDigest { get; init; } = string.Empty;
|
||||
public List<Finding> Findings { get; init; } = [];
|
||||
public List<VexStatement> VexStatements { get; init; } = [];
|
||||
}
|
||||
|
||||
public record Finding
|
||||
{
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
public string Severity { get; init; } = string.Empty;
|
||||
public bool ImpactUnknown { get; init; }
|
||||
public PackageRef Package { get; init; } = new();
|
||||
}
|
||||
|
||||
public record PackageRef
|
||||
{
|
||||
public string Purl { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public record VexStatement
|
||||
{
|
||||
public string VulnerabilityId { get; init; } = string.Empty;
|
||||
public VexStatus Status { get; init; }
|
||||
public string Justification { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum VexStatus { Unknown, NotAffected, Affected, Fixed, UnderInvestigation }
|
||||
|
||||
public record Violation
|
||||
{
|
||||
public string Code { get; init; } = string.Empty;
|
||||
public string Message { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
Reference in New Issue
Block a user