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,296 @@
// =============================================================================
// DeterministicTestFixtures.cs
// Deterministic test fixtures for TTFS testing
// Part of Task T15: Create deterministic test fixtures
// =============================================================================
using StellaOps.Orchestrator.Core.Domain;
namespace StellaOps.Orchestrator.Tests.Ttfs;
/// <summary>
/// Deterministic test fixtures for TTFS (Time-To-First-Signal) testing.
/// Uses frozen timestamps and pre-generated UUIDs for reproducibility.
/// </summary>
public static class DeterministicTestFixtures
{
/// <summary>
/// Frozen timestamp used across all fixtures.
/// </summary>
public static readonly DateTimeOffset FrozenTimestamp =
new(2025, 12, 4, 12, 0, 0, TimeSpan.Zero);
/// <summary>
/// Deterministic seed for reproducible random generation.
/// </summary>
public const int DeterministicSeed = 42;
/// <summary>
/// Pre-generated deterministic UUIDs.
/// </summary>
public static class Ids
{
public static readonly Guid TenantId = Guid.Parse("11111111-1111-1111-1111-111111111111");
public static readonly Guid RunId = Guid.Parse("22222222-2222-2222-2222-222222222222");
public static readonly Guid JobId = Guid.Parse("33333333-3333-3333-3333-333333333333");
public static readonly Guid SourceId = Guid.Parse("44444444-4444-4444-4444-444444444444");
public static readonly Guid SignatureId = Guid.Parse("55555555-5555-5555-5555-555555555555");
public const string TenantIdString = "test-tenant-deterministic";
public const string CorrelationId = "corr-deterministic-001";
public const string SignalId = "sig-deterministic-001";
}
/// <summary>
/// Deterministic digest values.
/// </summary>
public static class Digests
{
/// <summary>64-character lowercase hex digest (SHA-256).</summary>
public const string PayloadDigest = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
/// <summary>Image digest reference.</summary>
public const string ImageDigest = "sha256:abc123def456789012345678901234567890123456789012345678901234abcd";
}
/// <summary>
/// Creates a deterministic Run for testing.
/// </summary>
public static Run CreateRun(
Guid? runId = null,
string? tenantId = null,
RunStatus status = RunStatus.Pending,
DateTimeOffset? createdAt = null)
{
return new Run(
RunId: runId ?? Ids.RunId,
TenantId: tenantId ?? Ids.TenantIdString,
ProjectId: null,
SourceId: Ids.SourceId,
RunType: "scan",
Status: status,
CorrelationId: Ids.CorrelationId,
TotalJobs: 1,
CompletedJobs: 0,
SucceededJobs: 0,
FailedJobs: 0,
CreatedAt: createdAt ?? FrozenTimestamp,
StartedAt: null,
CompletedAt: null,
CreatedBy: "system",
Metadata: null);
}
/// <summary>
/// Creates a deterministic Job for testing.
/// </summary>
public static Job CreateJob(
Guid? jobId = null,
Guid? runId = null,
string? tenantId = null,
JobStatus status = JobStatus.Scheduled,
DateTimeOffset? createdAt = null)
{
return new Job(
JobId: jobId ?? Ids.JobId,
TenantId: tenantId ?? Ids.TenantIdString,
ProjectId: null,
RunId: runId ?? Ids.RunId,
JobType: "scan.image",
Status: status,
Priority: 0,
Attempt: 1,
MaxAttempts: 3,
PayloadDigest: Digests.PayloadDigest,
Payload: "{}",
IdempotencyKey: "idem-deterministic-001",
CorrelationId: Ids.CorrelationId,
LeaseId: null,
WorkerId: null,
TaskRunnerId: null,
LeaseUntil: null,
CreatedAt: createdAt ?? FrozenTimestamp,
ScheduledAt: createdAt ?? FrozenTimestamp,
LeasedAt: null,
CompletedAt: null,
NotBefore: null,
Reason: null,
ReplayOf: null,
CreatedBy: "system");
}
/// <summary>
/// Creates a deterministic FirstSignal for testing.
/// </summary>
public static FirstSignal CreateFirstSignal(
FirstSignalKind kind = FirstSignalKind.Queued,
FirstSignalPhase phase = FirstSignalPhase.Resolve,
bool cacheHit = false,
string source = "cold_start",
LastKnownOutcome? lastKnownOutcome = null)
{
return new FirstSignal
{
Version = "1.0",
SignalId = Ids.SignalId,
JobId = Ids.JobId,
Timestamp = FrozenTimestamp,
Kind = kind,
Phase = phase,
Scope = new FirstSignalScope
{
Type = "image",
Id = Digests.ImageDigest
},
Summary = GetSummaryForKind(kind),
EtaSeconds = kind == FirstSignalKind.Queued ? 120 : null,
LastKnownOutcome = lastKnownOutcome,
NextActions = GetActionsForKind(kind),
Diagnostics = new FirstSignalDiagnostics
{
CacheHit = cacheHit,
Source = source,
CorrelationId = Ids.CorrelationId
}
};
}
/// <summary>
/// Creates a deterministic LastKnownOutcome for testing.
/// </summary>
public static LastKnownOutcome CreateLastKnownOutcome(
string confidence = "high",
int hitCount = 15)
{
return new LastKnownOutcome
{
SignatureId = Ids.SignatureId.ToString(),
ErrorCode = "EDEPNOTFOUND",
Token = "EDEPNOTFOUND",
Excerpt = "Could not resolve dependency @types/node@^18.0.0",
Confidence = confidence,
FirstSeenAt = FrozenTimestamp.AddDays(-3),
HitCount = hitCount
};
}
/// <summary>
/// Creates a deterministic failed FirstSignal with LastKnownOutcome.
/// </summary>
public static FirstSignal CreateFailedSignalWithOutcome()
{
return CreateFirstSignal(
kind: FirstSignalKind.Failed,
phase: FirstSignalPhase.Analyze,
cacheHit: false,
source: "failure_index",
lastKnownOutcome: CreateLastKnownOutcome());
}
/// <summary>
/// Creates a deterministic succeeded FirstSignal.
/// </summary>
public static FirstSignal CreateSucceededSignal()
{
return CreateFirstSignal(
kind: FirstSignalKind.Succeeded,
phase: FirstSignalPhase.Report,
cacheHit: true,
source: "snapshot");
}
private static string GetSummaryForKind(FirstSignalKind kind)
{
return kind switch
{
FirstSignalKind.Queued => "Job queued, waiting for available worker",
FirstSignalKind.Started => "Analysis started",
FirstSignalKind.Phase => "Processing in progress",
FirstSignalKind.Blocked => "Blocked by policy: critical-vuln-gate",
FirstSignalKind.Failed => "Analysis failed: dependency resolution error",
FirstSignalKind.Succeeded => "Scan completed: 3 critical, 12 high, 45 medium findings",
FirstSignalKind.Canceled => "Job canceled by user",
FirstSignalKind.Unavailable => "Signal unavailable",
_ => "Unknown state"
};
}
private static IReadOnlyList<NextAction>? GetActionsForKind(FirstSignalKind kind)
{
return kind switch
{
FirstSignalKind.Failed => new[]
{
new NextAction
{
Type = "open_logs",
Label = "View Logs",
Target = $"/logs/{Ids.JobId}"
},
new NextAction
{
Type = "retry",
Label = "Retry Job",
Target = $"/retry/{Ids.JobId}"
}
},
FirstSignalKind.Succeeded => new[]
{
new NextAction
{
Type = "open_job",
Label = "View Results",
Target = $"/jobs/{Ids.JobId}"
}
},
FirstSignalKind.Blocked => new[]
{
new NextAction
{
Type = "docs",
Label = "Policy Details",
Target = "/docs/policies/critical-vuln-gate"
}
},
_ => null
};
}
}
/// <summary>
/// Seeded random number generator for deterministic test data.
/// </summary>
public sealed class SeededRandom
{
private readonly Random _random;
public SeededRandom(int seed = DeterministicTestFixtures.DeterministicSeed)
{
_random = new Random(seed);
}
public int Next() => _random.Next();
public int Next(int maxValue) => _random.Next(maxValue);
public int Next(int minValue, int maxValue) => _random.Next(minValue, maxValue);
public double NextDouble() => _random.NextDouble();
/// <summary>
/// Generates a deterministic GUID based on the seed.
/// </summary>
public Guid NextGuid()
{
var bytes = new byte[16];
_random.NextBytes(bytes);
return new Guid(bytes);
}
/// <summary>
/// Generates a deterministic hex string.
/// </summary>
public string NextHexString(int length)
{
var bytes = new byte[length / 2];
_random.NextBytes(bytes);
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}

View File

@@ -0,0 +1,139 @@
// =============================================================================
// DeterministicTestFixturesTests.cs
// Tests for deterministic test fixtures
// Part of Task T15: Create deterministic test fixtures
// =============================================================================
using StellaOps.Orchestrator.Core.Domain;
namespace StellaOps.Orchestrator.Tests.Ttfs;
public sealed class DeterministicTestFixturesTests
{
[Fact]
public void FrozenTimestamp_IsCorrectDate()
{
var expected = new DateTimeOffset(2025, 12, 4, 12, 0, 0, TimeSpan.Zero);
Assert.Equal(expected, DeterministicTestFixtures.FrozenTimestamp);
}
[Fact]
public void Ids_AreConsistent_AcrossMultipleCalls()
{
var runId1 = DeterministicTestFixtures.Ids.RunId;
var runId2 = DeterministicTestFixtures.Ids.RunId;
Assert.Equal(runId1, runId2);
Assert.Equal("22222222-2222-2222-2222-222222222222", runId1.ToString());
}
[Fact]
public void CreateRun_ReturnsDeterministicRun()
{
var run1 = DeterministicTestFixtures.CreateRun();
var run2 = DeterministicTestFixtures.CreateRun();
Assert.Equal(run1.RunId, run2.RunId);
Assert.Equal(run1.TenantId, run2.TenantId);
Assert.Equal(run1.CreatedAt, run2.CreatedAt);
Assert.Equal(run1.CorrelationId, run2.CorrelationId);
}
[Fact]
public void CreateJob_ReturnsDeterministicJob()
{
var job1 = DeterministicTestFixtures.CreateJob();
var job2 = DeterministicTestFixtures.CreateJob();
Assert.Equal(job1.JobId, job2.JobId);
Assert.Equal(job1.TenantId, job2.TenantId);
Assert.Equal(job1.PayloadDigest, job2.PayloadDigest);
Assert.Equal(64, job1.PayloadDigest.Length);
}
[Fact]
public void CreateFirstSignal_ReturnsDeterministicSignal()
{
var signal1 = DeterministicTestFixtures.CreateFirstSignal();
var signal2 = DeterministicTestFixtures.CreateFirstSignal();
Assert.Equal(signal1.SignalId, signal2.SignalId);
Assert.Equal(signal1.JobId, signal2.JobId);
Assert.Equal(signal1.Timestamp, signal2.Timestamp);
Assert.Equal(signal1.Diagnostics.CorrelationId, signal2.Diagnostics.CorrelationId);
}
[Fact]
public void CreateFailedSignalWithOutcome_IncludesLastKnownOutcome()
{
var signal = DeterministicTestFixtures.CreateFailedSignalWithOutcome();
Assert.Equal(FirstSignalKind.Failed, signal.Kind);
Assert.NotNull(signal.LastKnownOutcome);
Assert.Equal("EDEPNOTFOUND", signal.LastKnownOutcome.ErrorCode);
Assert.Equal("high", signal.LastKnownOutcome.Confidence);
Assert.Equal(15, signal.LastKnownOutcome.HitCount);
}
[Fact]
public void CreateSucceededSignal_HasCorrectProperties()
{
var signal = DeterministicTestFixtures.CreateSucceededSignal();
Assert.Equal(FirstSignalKind.Succeeded, signal.Kind);
Assert.Equal(FirstSignalPhase.Report, signal.Phase);
Assert.True(signal.Diagnostics.CacheHit);
Assert.Equal("snapshot", signal.Diagnostics.Source);
Assert.Null(signal.LastKnownOutcome);
}
[Fact]
public void SeededRandom_ProducesDeterministicSequence()
{
var rng1 = new SeededRandom(42);
var rng2 = new SeededRandom(42);
var values1 = Enumerable.Range(0, 10).Select(_ => rng1.Next()).ToList();
var values2 = Enumerable.Range(0, 10).Select(_ => rng2.Next()).ToList();
Assert.Equal(values1, values2);
}
[Fact]
public void SeededRandom_NextGuid_ProducesDeterministicGuids()
{
var rng1 = new SeededRandom(42);
var rng2 = new SeededRandom(42);
var guid1 = rng1.NextGuid();
var guid2 = rng2.NextGuid();
Assert.Equal(guid1, guid2);
Assert.NotEqual(Guid.Empty, guid1);
}
[Fact]
public void SeededRandom_NextHexString_ProducesDeterministicStrings()
{
var rng1 = new SeededRandom(42);
var rng2 = new SeededRandom(42);
var hex1 = rng1.NextHexString(64);
var hex2 = rng2.NextHexString(64);
Assert.Equal(hex1, hex2);
Assert.Equal(64, hex1.Length);
Assert.Matches("^[a-f0-9]+$", hex1);
}
[Fact]
public void Digests_AreValidFormats()
{
// PayloadDigest should be 64-char hex
Assert.Equal(64, DeterministicTestFixtures.Digests.PayloadDigest.Length);
Assert.Matches("^[a-f0-9]+$", DeterministicTestFixtures.Digests.PayloadDigest);
// ImageDigest should be sha256: prefixed
Assert.StartsWith("sha256:", DeterministicTestFixtures.Digests.ImageDigest);
}
}