Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -0,0 +1,232 @@
// -----------------------------------------------------------------------------
// FidelityMetricsIntegrationTests.cs
// Sprint: SPRINT_3403_0001_0001_fidelity_metrics
// Task: FID-3403-013
// Description: Integration tests for fidelity metrics in determinism harness
// -----------------------------------------------------------------------------
using StellaOps.Scanner.Worker.Determinism;
using StellaOps.Scanner.Worker.Determinism.Calculators;
using Xunit;
namespace StellaOps.Scanner.Worker.Tests.Determinism;
public sealed class FidelityMetricsIntegrationTests
{
[Fact]
public void DeterminismReport_WithFidelityMetrics_IncludesAllThreeTiers()
{
// Arrange & Act
var fidelity = CreateTestFidelityMetrics(
bitwiseFidelity: 0.98,
semanticFidelity: 0.99,
policyFidelity: 1.0);
var report = new DeterminismReport(
Version: "1.0.0",
Release: "test-release",
Platform: "linux-amd64",
PolicySha: "sha256:policy123",
FeedsSha: "sha256:feeds456",
ScannerSha: "sha256:scanner789",
OverallScore: 0.98,
ThresholdOverall: 0.95,
ThresholdImage: 0.90,
Images: [],
Fidelity: fidelity);
// Assert
Assert.NotNull(report.Fidelity);
Assert.Equal(0.98, report.Fidelity.BitwiseFidelity);
Assert.Equal(0.99, report.Fidelity.SemanticFidelity);
Assert.Equal(1.0, report.Fidelity.PolicyFidelity);
}
[Fact]
public void DeterminismImageReport_WithFidelityMetrics_TracksPerImage()
{
// Arrange
var imageFidelity = CreateTestFidelityMetrics(
bitwiseFidelity: 0.95,
semanticFidelity: 0.98,
policyFidelity: 1.0);
var imageReport = new DeterminismImageReport(
Image: "sha256:image123",
Runs: 5,
Identical: 4,
Score: 0.80,
ArtifactHashes: new Dictionary<string, string>(),
RunsDetail: [],
Fidelity: imageFidelity);
// Assert
Assert.NotNull(imageReport.Fidelity);
Assert.Equal(0.95, imageReport.Fidelity.BitwiseFidelity);
Assert.Equal(5, imageReport.Fidelity.TotalReplays);
}
[Fact]
public void FidelityMetricsService_ComputesAllThreeTiers()
{
// Arrange
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
var baseline = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass");
var replay = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass");
// Act
var metrics = service.Compute(baseline, new[] { replay });
// Assert
Assert.Equal(1, metrics.TotalReplays);
Assert.True(metrics.BitwiseFidelity >= 0.0 && metrics.BitwiseFidelity <= 1.0);
Assert.True(metrics.SemanticFidelity >= 0.0 && metrics.SemanticFidelity <= 1.0);
Assert.True(metrics.PolicyFidelity >= 0.0 && metrics.PolicyFidelity <= 1.0);
}
[Fact]
public void FidelityMetrics_SemanticEquivalent_ButBitwiseDifferent()
{
// Arrange - same semantic content, different formatting/ordering
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
var baseline = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "HIGH", "pass");
var replay = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass"); // case difference
// Act
var metrics = service.Compute(baseline, new[] { replay });
// Assert
// Bitwise should be < 1.0 (different bytes)
// Semantic should be 1.0 (same meaning)
// Policy should be 1.0 (same decision)
Assert.True(metrics.SemanticFidelity >= metrics.BitwiseFidelity);
Assert.Equal(1.0, metrics.PolicyFidelity);
}
[Fact]
public void FidelityMetrics_PolicyDifference_ReflectedInPF()
{
// Arrange
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
var baseline = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass");
var replay = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "fail"); // policy differs
// Act
var metrics = service.Compute(baseline, new[] { replay });
// Assert
Assert.True(metrics.PolicyFidelity < 1.0);
}
[Fact]
public void FidelityMetrics_MultipleReplays_AveragesCorrectly()
{
// Arrange
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
var baseline = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass");
var replays = new[]
{
CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass"), // identical
CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass"), // identical
CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "fail"), // policy diff
};
// Act
var metrics = service.Compute(baseline, replays);
// Assert
Assert.Equal(3, metrics.TotalReplays);
// 2 out of 3 have matching policy
Assert.True(metrics.PolicyFidelity >= 0.6 && metrics.PolicyFidelity <= 0.7);
}
[Fact]
public void FidelityMetrics_IncludesMismatchDiagnostics()
{
// Arrange
var service = new FidelityMetricsService(
new BitwiseFidelityCalculator(),
new SemanticFidelityCalculator(),
new PolicyFidelityCalculator());
var baseline = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "high", "pass");
var replay = CreateTestScanResult("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "critical", "fail"); // semantic + policy diff
// Act
var metrics = service.Compute(baseline, new[] { replay });
// Assert
Assert.NotNull(metrics.Mismatches);
Assert.NotEmpty(metrics.Mismatches);
}
private static FidelityMetrics CreateTestFidelityMetrics(
double bitwiseFidelity,
double semanticFidelity,
double policyFidelity,
int totalReplays = 5)
{
return new FidelityMetrics
{
BitwiseFidelity = bitwiseFidelity,
SemanticFidelity = semanticFidelity,
PolicyFidelity = policyFidelity,
TotalReplays = totalReplays,
IdenticalOutputs = (int)(totalReplays * bitwiseFidelity),
SemanticMatches = (int)(totalReplays * semanticFidelity),
PolicyMatches = (int)(totalReplays * policyFidelity),
ComputedAt = DateTimeOffset.UtcNow
};
}
private static TestScanResult CreateTestScanResult(
string purl,
string cve,
string severity,
string policyDecision)
{
return new TestScanResult
{
Packages = new[] { new TestPackage { Purl = purl } },
Findings = new[] { new TestFinding { Cve = cve, Severity = severity } },
PolicyDecision = policyDecision,
PolicyReasonCodes = policyDecision == "pass" ? Array.Empty<string>() : new[] { "severity_exceeded" }
};
}
// Test support types
private sealed record TestScanResult
{
public required IReadOnlyList<TestPackage> Packages { get; init; }
public required IReadOnlyList<TestFinding> Findings { get; init; }
public required string PolicyDecision { get; init; }
public required IReadOnlyList<string> PolicyReasonCodes { get; init; }
}
private sealed record TestPackage
{
public required string Purl { get; init; }
}
private sealed record TestFinding
{
public required string Cve { get; init; }
public required string Severity { get; init; }
}
}

View File

@@ -0,0 +1,217 @@
// -----------------------------------------------------------------------------
// ScanCompletionMetricsIntegrationTests.cs
// Sprint: SPRINT_3406_0001_0001_metrics_tables
// Task: METRICS-3406-012
// Description: Integration test verifying metrics captured on scan completion
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Scanner.Storage.Models;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.Worker.Metrics;
using Xunit;
namespace StellaOps.Scanner.Worker.Tests.Metrics;
public sealed class ScanCompletionMetricsIntegrationTests
{
[Fact]
public async Task CaptureAsync_PersistsMetricsOnScanCompletion()
{
// Arrange
var savedMetrics = new List<ScanMetrics>();
var savedPhases = new List<ExecutionPhase>();
var mockRepository = new Mock<IScanMetricsRepository>();
mockRepository
.Setup(r => r.SaveAsync(It.IsAny<ScanMetrics>(), It.IsAny<CancellationToken>()))
.Callback<ScanMetrics, CancellationToken>((m, _) => savedMetrics.Add(m))
.Returns(Task.CompletedTask);
mockRepository
.Setup(r => r.SavePhasesAsync(It.IsAny<IEnumerable<ExecutionPhase>>(), It.IsAny<CancellationToken>()))
.Callback<IEnumerable<ExecutionPhase>, CancellationToken>((p, _) => savedPhases.AddRange(p))
.Returns(Task.CompletedTask);
var factory = new TestScanMetricsCollectorFactory(mockRepository.Object);
var integration = new ScanCompletionMetricsIntegration(
factory,
NullLogger<ScanCompletionMetricsIntegration>.Instance);
var context = new ScanCompletionContext
{
ScanId = Guid.NewGuid(),
TenantId = Guid.NewGuid(),
ArtifactDigest = "sha256:abc123",
ArtifactType = "oci_image",
FindingsSha256 = "sha256:def456",
PackageCount = 150,
FindingCount = 25,
VexDecisionCount = 10,
Phases = new[]
{
new PhaseCompletionInfo
{
PhaseName = "pull",
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-10),
FinishedAt = DateTimeOffset.UtcNow.AddSeconds(-5),
Success = true
},
new PhaseCompletionInfo
{
PhaseName = "analyze",
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-5),
FinishedAt = DateTimeOffset.UtcNow,
Success = true
}
}
};
// Act
await integration.CaptureAsync(context);
// Assert
Assert.Single(savedMetrics);
var metrics = savedMetrics[0];
Assert.Equal(context.ScanId, metrics.ScanId);
Assert.Equal(context.TenantId, metrics.TenantId);
Assert.Equal(context.ArtifactDigest, metrics.ArtifactDigest);
Assert.Equal(context.FindingsSha256, metrics.FindingsSha256);
Assert.Equal(150, metrics.PackageCount);
Assert.Equal(25, metrics.FindingCount);
}
[Fact]
public async Task CaptureAsync_DoesNotFailScanOnMetricsError()
{
// Arrange
var mockRepository = new Mock<IScanMetricsRepository>();
mockRepository
.Setup(r => r.SaveAsync(It.IsAny<ScanMetrics>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("Database error"));
var factory = new TestScanMetricsCollectorFactory(mockRepository.Object);
var integration = new ScanCompletionMetricsIntegration(
factory,
NullLogger<ScanCompletionMetricsIntegration>.Instance);
var context = new ScanCompletionContext
{
ScanId = Guid.NewGuid(),
TenantId = Guid.NewGuid(),
ArtifactDigest = "sha256:abc123",
ArtifactType = "oci_image",
FindingsSha256 = "sha256:def456"
};
// Act & Assert - should not throw
await integration.CaptureAsync(context);
}
[Fact]
public async Task CaptureAsync_IncludesVexAndProofDigests()
{
// Arrange
var savedMetrics = new List<ScanMetrics>();
var mockRepository = new Mock<IScanMetricsRepository>();
mockRepository
.Setup(r => r.SaveAsync(It.IsAny<ScanMetrics>(), It.IsAny<CancellationToken>()))
.Callback<ScanMetrics, CancellationToken>((m, _) => savedMetrics.Add(m))
.Returns(Task.CompletedTask);
mockRepository
.Setup(r => r.SavePhasesAsync(It.IsAny<IEnumerable<ExecutionPhase>>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var factory = new TestScanMetricsCollectorFactory(mockRepository.Object);
var integration = new ScanCompletionMetricsIntegration(
factory,
NullLogger<ScanCompletionMetricsIntegration>.Instance);
var context = new ScanCompletionContext
{
ScanId = Guid.NewGuid(),
TenantId = Guid.NewGuid(),
ArtifactDigest = "sha256:abc123",
ArtifactType = "oci_image",
FindingsSha256 = "sha256:findings",
VexBundleSha256 = "sha256:vex",
ProofBundleSha256 = "sha256:proof",
SbomSha256 = "sha256:sbom"
};
// Act
await integration.CaptureAsync(context);
// Assert
var metrics = savedMetrics[0];
Assert.Equal("sha256:vex", metrics.VexBundleSha256);
Assert.Equal("sha256:proof", metrics.ProofBundleSha256);
Assert.Equal("sha256:sbom", metrics.SbomSha256);
}
[Fact]
public async Task CaptureAsync_IncludesReplayMetadata()
{
// Arrange
var savedMetrics = new List<ScanMetrics>();
var mockRepository = new Mock<IScanMetricsRepository>();
mockRepository
.Setup(r => r.SaveAsync(It.IsAny<ScanMetrics>(), It.IsAny<CancellationToken>()))
.Callback<ScanMetrics, CancellationToken>((m, _) => savedMetrics.Add(m))
.Returns(Task.CompletedTask);
mockRepository
.Setup(r => r.SavePhasesAsync(It.IsAny<IEnumerable<ExecutionPhase>>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var factory = new TestScanMetricsCollectorFactory(mockRepository.Object);
var integration = new ScanCompletionMetricsIntegration(
factory,
NullLogger<ScanCompletionMetricsIntegration>.Instance);
var context = new ScanCompletionContext
{
ScanId = Guid.NewGuid(),
TenantId = Guid.NewGuid(),
ArtifactDigest = "sha256:abc123",
ArtifactType = "oci_image",
FindingsSha256 = "sha256:findings",
IsReplay = true,
ReplayManifestHash = "sha256:replay123"
};
// Act
await integration.CaptureAsync(context);
// Assert
var metrics = savedMetrics[0];
Assert.True(metrics.IsReplay);
Assert.Equal("sha256:replay123", metrics.ReplayManifestHash);
}
/// <summary>
/// Test factory that uses a mock repository.
/// </summary>
private sealed class TestScanMetricsCollectorFactory : IScanMetricsCollectorFactory
{
private readonly IScanMetricsRepository _repository;
public TestScanMetricsCollectorFactory(IScanMetricsRepository repository)
{
_repository = repository;
}
public ScanMetricsCollector Create(Guid scanId, Guid tenantId, string artifactDigest, string artifactType)
{
return new ScanMetricsCollector(
_repository,
NullLogger<ScanMetricsCollector>.Instance,
scanId,
tenantId,
artifactDigest,
artifactType,
"test-1.0.0");
}
}
}