Sprint: SPRINT_20251229_049_BE_csproj_audit_maint_tests Tasks: AUDIT-0001 through AUDIT-0147 APPLY tasks (approved decisions 1-9) Changes: - Set TreatWarningsAsErrors=true for all production .NET projects - Fixed nullable warnings in Scanner.EntryTrace, Scanner.Evidence, Scheduler.Worker, Concelier connectors, and other modules - Injected TimeProvider/IGuidProvider for deterministic time/ID generation - Added path traversal validation in AirGap.Bundle - Fixed NULL handling in various cursor classes - Third-party GostCryptography retains TreatWarningsAsErrors=false (preserves original) - Test projects excluded per user decision (rejected decision 10) Note: All 17 ACSC connector tests pass after snapshot fixture sync
StellaOps.Evidence.Core
Unified evidence model library providing content-addressed, cryptographically verifiable evidence records for the StellaOps platform.
Overview
This library defines the core evidence model that unifies all evidence types across StellaOps modules. Evidence records are:
- Content-addressed: Each record has a deterministic ID derived from its content
- Cryptographically verifiable: Records can carry signatures from their producers
- Linked: Records reference their sources (subjects) and can form chains
- Typed: Each record has a well-defined type for semantic clarity
Key Types
IEvidence
The core evidence interface that all evidence records implement:
public interface IEvidence
{
string EvidenceId { get; } // Content-addressed ID
EvidenceType Type { get; } // Evidence type enum
string SubjectNodeId { get; } // What this evidence is about
DateTimeOffset CreatedAt { get; } // UTC timestamp
IReadOnlyList<EvidenceSignature> Signatures { get; } // Cryptographic signatures
EvidenceProvenance? Provenance { get; } // Origin information
IReadOnlyDictionary<string, string> Properties { get; } // Type-specific data
}
EvidenceType
Enumeration of all supported evidence types:
| Type | Description |
|---|---|
Unknown |
Unspecified evidence type |
Sbom |
Software Bill of Materials |
Vulnerability |
Vulnerability finding |
Vex |
VEX statement (exploitability) |
Attestation |
DSSE/in-toto attestation |
PolicyDecision |
Policy evaluation result |
ScanResult |
Scanner output |
Provenance |
SLSA provenance |
Signature |
Cryptographic signature |
ProofSegment |
Proof chain segment |
Exception |
Policy exception/waiver |
Advisory |
Security advisory |
CveMatch |
CVE to component match |
ReachabilityResult |
Code reachability analysis |
EvidenceRecord
The standard implementation of IEvidence:
public sealed record EvidenceRecord : IEvidence
{
public required string EvidenceId { get; init; }
public required EvidenceType Type { get; init; }
public required string SubjectNodeId { get; init; }
public required DateTimeOffset CreatedAt { get; init; }
public IReadOnlyList<EvidenceSignature> Signatures { get; init; } = [];
public EvidenceProvenance? Provenance { get; init; }
public IReadOnlyDictionary<string, string> Properties { get; init; } =
new Dictionary<string, string>();
}
Adapters
The library provides adapters to convert module-specific types to unified evidence records:
| Adapter | Source Module | Source Type |
|---|---|---|
EvidenceStatementAdapter |
Attestor | EvidenceStatement |
ProofSegmentAdapter |
Scanner | ProofSegment |
VexObservationAdapter |
Excititor | VexObservation |
ExceptionApplicationAdapter |
Policy | ExceptionApplication |
Using Adapters
// Convert a VEX observation to evidence records
var adapter = new VexObservationAdapter();
var input = new VexObservationInput
{
SubjectDigest = imageDigest,
Upstream = new VexObservationUpstreamInput { ... },
Statements = new[] { ... }
};
var records = adapter.ToEvidence(input);
Storage
IEvidenceStore
Interface for evidence persistence:
public interface IEvidenceStore
{
Task<IEvidence?> GetAsync(string evidenceId, CancellationToken ct = default);
Task<IReadOnlyList<IEvidence>> GetBySubjectAsync(string subjectNodeId, CancellationToken ct = default);
Task<IReadOnlyList<IEvidence>> GetByTypeAsync(EvidenceType type, CancellationToken ct = default);
Task StoreAsync(IEvidence evidence, CancellationToken ct = default);
Task<bool> ExistsAsync(string evidenceId, CancellationToken ct = default);
}
InMemoryEvidenceStore
Thread-safe in-memory implementation for testing and caching:
var store = new InMemoryEvidenceStore();
await store.StoreAsync(evidenceRecord);
var retrieved = await store.GetAsync(evidenceRecord.EvidenceId);
Usage Examples
Creating Evidence Records
var evidence = new EvidenceRecord
{
EvidenceId = "sha256:abc123...",
Type = EvidenceType.Vulnerability,
SubjectNodeId = componentId,
CreatedAt = DateTimeOffset.UtcNow,
Signatures = new[]
{
new EvidenceSignature
{
SignerId = "scanner/grype",
Algorithm = "Ed25519",
SignatureBase64 = "...",
SignedAt = DateTimeOffset.UtcNow,
SignerType = SignerType.Tool
}
},
Properties = new Dictionary<string, string>
{
["cve"] = "CVE-2024-1234",
["severity"] = "HIGH",
["cvss"] = "8.5"
}
};
Querying Evidence
var store = serviceProvider.GetRequiredService<IEvidenceStore>();
// Get all evidence for a specific subject
var subjectEvidence = await store.GetBySubjectAsync(componentId);
// Get all VEX statements
var vexRecords = await store.GetByTypeAsync(EvidenceType.Vex);
// Check if evidence exists
var exists = await store.ExistsAsync(evidenceId);
Integration
Dependency Injection
services.AddSingleton<IEvidenceStore, InMemoryEvidenceStore>();
// Or for PostgreSQL:
// services.AddScoped<IEvidenceStore, PostgresEvidenceStore>();
Related Documentation
- Unified Evidence Model - Architecture overview
- Graph Root Attestation - Evidence in attestations