up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 20:55:22 +02:00
parent d040c001ac
commit 2548abc56f
231 changed files with 47468 additions and 68 deletions

View File

@@ -0,0 +1,81 @@
using System.IO;
using System.Text.Json;
using FluentAssertions;
using StellaOps.Policy.Scoring.Policies;
using Xunit;
namespace StellaOps.Policy.Scoring.Tests;
public sealed class CvssPolicyLoaderTests
{
private readonly CvssPolicyLoader _loader = new();
[Fact]
public void Load_ValidPolicy_ComputesDeterministicHashAndReturnsPolicy()
{
// Arrange
var json = """
{
"policyId": "default",
"version": "1.0.0",
"name": "Default CVSS v4",
"effectiveFrom": "2025-01-01T00:00:00Z",
"severityThresholds": { "lowMin": 0.1, "mediumMin": 4.0, "highMin": 7.0, "criticalMin": 9.0 },
"metricOverrides": [
{ "id": "override-1", "vulnerabilityPattern": "CVE-2025-0001", "priority": 1, "scoreAdjustment": 0.3, "isActive": true }
],
"attestationRequirements": { "requireDsse": true, "requireRekor": false }
}
""";
// Act
var result = _loader.Load(json);
// Assert
result.IsValid.Should().BeTrue();
result.Policy.Should().NotBeNull();
result.Hash.Should().NotBeNullOrWhiteSpace();
// determinism: hash must match when reloading the same payload (even with hash field present)
var withHash = JsonSerializer.Deserialize<JsonElement>(json);
var roundTrip = _loader.Load(AddHash(withHash, result.Hash!));
roundTrip.Hash.Should().Be(result.Hash);
roundTrip.Policy!.Hash.Should().Be(result.Hash);
}
[Fact]
public void Load_InvalidPolicy_ReturnsValidationErrors()
{
// Arrange: missing required fields
const string json = """{"name":"Missing required fields"}""";
// Act
var result = _loader.Load(json);
// Assert
result.IsValid.Should().BeFalse();
result.Policy.Should().BeNull();
result.Errors.Should().NotBeEmpty();
}
private static JsonElement AddHash(JsonElement element, string hash)
{
using var doc = JsonDocument.Parse(element.GetRawText());
using var stream = new MemoryStream();
using (var writer = new Utf8JsonWriter(stream))
{
writer.WriteStartObject();
foreach (var prop in doc.RootElement.EnumerateObject())
{
writer.WritePropertyName(prop.Name);
prop.Value.WriteTo(writer);
}
writer.WriteString("hash", hash);
writer.WriteEndObject();
}
stream.Seek(0, SeekOrigin.Begin);
using var finalDoc = JsonDocument.Parse(stream);
return finalDoc.RootElement.Clone();
}
}

View File

@@ -0,0 +1,17 @@
using System.Collections.Concurrent;
using StellaOps.Policy.Scoring.Receipts;
namespace StellaOps.Policy.Scoring.Tests.Fakes;
internal sealed class InMemoryReceiptRepository : IReceiptRepository
{
private readonly ConcurrentDictionary<string, CvssScoreReceipt> _store = new();
public Task<CvssScoreReceipt> SaveAsync(CvssScoreReceipt receipt, CancellationToken cancellationToken = default)
{
_store[receipt.ReceiptId] = receipt;
return Task.FromResult(receipt);
}
public bool Contains(string receiptId) => _store.ContainsKey(receiptId);
}

View File

@@ -0,0 +1,129 @@
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.Policy.Scoring.Engine;
using StellaOps.Policy.Scoring.Receipts;
using StellaOps.Policy.Scoring.Tests.Fakes;
using Xunit;
namespace StellaOps.Policy.Scoring.Tests;
public sealed class ReceiptBuilderTests
{
private readonly ICvssV4Engine _engine = new CvssV4Engine();
private readonly InMemoryReceiptRepository _repository = new();
[Fact]
public async Task CreateAsync_ComputesDeterministicHashAndStoresReceipt()
{
// Arrange
var policy = new CvssPolicy
{
PolicyId = "default",
Version = "1.0.0",
Name = "Default",
EffectiveFrom = new DateTimeOffset(2025, 01, 01, 0, 0, 0, TimeSpan.Zero),
Hash = "abc123",
SeverityThresholds = new CvssSeverityThresholds()
};
var request = new CreateReceiptRequest
{
VulnerabilityId = "CVE-2025-0001",
TenantId = "tenant-a",
CreatedBy = "tester",
CreatedAt = new DateTimeOffset(2025, 11, 28, 12, 0, 0, TimeSpan.Zero),
Policy = policy,
BaseMetrics = new CvssBaseMetrics
{
AttackVector = AttackVector.Network,
AttackComplexity = AttackComplexity.Low,
AttackRequirements = AttackRequirements.None,
PrivilegesRequired = PrivilegesRequired.None,
UserInteraction = UserInteraction.None,
VulnerableSystemConfidentiality = ImpactMetricValue.High,
VulnerableSystemIntegrity = ImpactMetricValue.High,
VulnerableSystemAvailability = ImpactMetricValue.High,
SubsequentSystemConfidentiality = ImpactMetricValue.High,
SubsequentSystemIntegrity = ImpactMetricValue.High,
SubsequentSystemAvailability = ImpactMetricValue.High
},
Evidence = ImmutableList<CvssEvidenceItem>.Empty.Add(new CvssEvidenceItem
{
Type = "advisory",
Uri = "sha256:deadbeef",
Description = "Vendor advisory",
IsAuthoritative = true
})
};
var builder = new ReceiptBuilder(_engine, _repository);
// Act
var receipt1 = await builder.CreateAsync(request);
var receipt2 = await builder.CreateAsync(request);
// Assert
receipt1.ReceiptId.Should().NotBeNullOrEmpty();
receipt1.VectorString.Should().StartWith("CVSS:4.0");
receipt1.InputHash.Should().NotBeNullOrEmpty();
receipt2.InputHash.Should().Be(receipt1.InputHash); // deterministic across runs with same inputs
_repository.Contains(receipt1.ReceiptId).Should().BeTrue();
}
[Fact]
public async Task CreateAsync_EnforcesEvidenceRequirements()
{
// Arrange
var policy = new CvssPolicy
{
PolicyId = "strict",
Version = "1.0.0",
Name = "Strict Evidence",
EffectiveFrom = DateTimeOffset.UtcNow,
Hash = "abc123",
EvidenceRequirements = new CvssEvidenceRequirements
{
MinimumCount = 2,
RequireAuthoritative = true,
RequiredTypes = ImmutableList.Create("advisory", "scan")
}
};
var request = new CreateReceiptRequest
{
VulnerabilityId = "CVE-2025-0002",
TenantId = "tenant-b",
CreatedBy = "tester",
Policy = policy,
BaseMetrics = new CvssBaseMetrics
{
AttackVector = AttackVector.Network,
AttackComplexity = AttackComplexity.Low,
AttackRequirements = AttackRequirements.None,
PrivilegesRequired = PrivilegesRequired.None,
UserInteraction = UserInteraction.None,
VulnerableSystemConfidentiality = ImpactMetricValue.High,
VulnerableSystemIntegrity = ImpactMetricValue.High,
VulnerableSystemAvailability = ImpactMetricValue.High,
SubsequentSystemConfidentiality = ImpactMetricValue.High,
SubsequentSystemIntegrity = ImpactMetricValue.High,
SubsequentSystemAvailability = ImpactMetricValue.High
},
Evidence = ImmutableList<CvssEvidenceItem>.Empty.Add(new CvssEvidenceItem
{
Type = "advisory",
Uri = "sha256:123",
IsAuthoritative = false
})
};
var builder = new ReceiptBuilder(_engine, _repository);
// Act
var act = async () => await builder.CreateAsync(request);
// Assert
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("*Evidence*");
}
}

View File

@@ -9,13 +9,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>