update evidence bundle to include new evidence types and implement ProofSpine integration
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-15 09:15:30 +02:00
parent 8c8f0c632d
commit 505fe7a885
49 changed files with 4756 additions and 551 deletions

View File

@@ -0,0 +1,23 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>Call stack snippet evidence.</summary>
public sealed class CallStackEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public IReadOnlyList<StackFrame>? Frames { get; init; }
public int? SinkFrameIndex { get; init; }
public int? SourceFrameIndex { get; init; }
public string? UnavailableReason { get; init; }
}
public sealed class StackFrame
{
public required string FunctionName { get; init; }
public required string FilePath { get; init; }
public required int Line { get; init; }
public int? Column { get; init; }
public string? SourceSnippet { get; init; }
public bool IsSink { get; init; }
public bool IsSource { get; init; }
}

View File

@@ -0,0 +1,26 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>SBOM/VEX diff evidence.</summary>
public sealed class DiffEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public DiffType DiffType { get; init; }
public IReadOnlyList<DiffEntry>? Entries { get; init; }
public string? PreviousScanId { get; init; }
public DateTimeOffset? PreviousScanTime { get; init; }
public string? UnavailableReason { get; init; }
}
public enum DiffType { Sbom, Vex, Combined }
public sealed class DiffEntry
{
public required DiffOperation Operation { get; init; }
public required string Path { get; init; }
public string? OldValue { get; init; }
public string? NewValue { get; init; }
public string? ComponentPurl { get; init; }
}
public enum DiffOperation { Added, Removed, Modified }

View File

@@ -1,6 +1,6 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>A complete evidence bundle for a single finding/alert. Contains all evidence required for triage decision.</summary>
/// <summary>A complete evidence bundle for a single finding/alert.</summary>
public sealed class EvidenceBundle
{
public string BundleId { get; init; } = Guid.NewGuid().ToString("N");
@@ -16,7 +16,6 @@ public sealed class EvidenceBundle
public required EvidenceHashSet Hashes { get; init; }
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>Compute evidence completeness score (0-4 based on core evidence types).</summary>
public int ComputeCompletenessScore()
{
var score = 0;
@@ -27,7 +26,6 @@ public sealed class EvidenceBundle
return score;
}
/// <summary>Create status summary from evidence.</summary>
public EvidenceStatusSummary CreateStatusSummary() => new()
{
Reachability = Reachability?.Status ?? EvidenceStatus.Unavailable,
@@ -38,7 +36,6 @@ public sealed class EvidenceBundle
GraphRevision = GraphRevision?.Status ?? EvidenceStatus.Unavailable
};
/// <summary>Create DSSE predicate for signing.</summary>
public EvidenceBundlePredicate ToSigningPredicate() => new()
{
BundleId = BundleId,

View File

@@ -13,28 +13,22 @@ public sealed class EvidenceBundleBuilder
private DiffEvidence? _diff;
private GraphRevisionEvidence? _graphRevision;
public EvidenceBundleBuilder(TimeProvider timeProvider)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public EvidenceBundleBuilder(TimeProvider timeProvider) => _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
public EvidenceBundleBuilder() : this(TimeProvider.System) { }
public EvidenceBundleBuilder WithAlertId(string alertId) { _alertId = alertId; return this; }
public EvidenceBundleBuilder WithArtifactId(string artifactId) { _artifactId = artifactId; return this; }
public EvidenceBundleBuilder WithReachability(ReachabilityEvidence evidence) { _reachability = evidence; return this; }
public EvidenceBundleBuilder WithCallStack(CallStackEvidence evidence) { _callStack = evidence; return this; }
public EvidenceBundleBuilder WithProvenance(ProvenanceEvidence evidence) { _provenance = evidence; return this; }
public EvidenceBundleBuilder WithVexStatus(VexStatusEvidence evidence) { _vexStatus = evidence; return this; }
public EvidenceBundleBuilder WithDiff(DiffEvidence evidence) { _diff = evidence; return this; }
public EvidenceBundleBuilder WithGraphRevision(GraphRevisionEvidence evidence) { _graphRevision = evidence; return this; }
public EvidenceBundleBuilder WithReachability(ReachabilityEvidence e) { _reachability = e; return this; }
public EvidenceBundleBuilder WithCallStack(CallStackEvidence e) { _callStack = e; return this; }
public EvidenceBundleBuilder WithProvenance(ProvenanceEvidence e) { _provenance = e; return this; }
public EvidenceBundleBuilder WithVexStatus(VexStatusEvidence e) { _vexStatus = e; return this; }
public EvidenceBundleBuilder WithDiff(DiffEvidence e) { _diff = e; return this; }
public EvidenceBundleBuilder WithGraphRevision(GraphRevisionEvidence e) { _graphRevision = e; return this; }
public EvidenceBundle Build()
{
if (string.IsNullOrWhiteSpace(_alertId))
throw new InvalidOperationException("AlertId is required");
if (string.IsNullOrWhiteSpace(_artifactId))
throw new InvalidOperationException("ArtifactId is required");
if (string.IsNullOrWhiteSpace(_alertId)) throw new InvalidOperationException("AlertId is required");
if (string.IsNullOrWhiteSpace(_artifactId)) throw new InvalidOperationException("ArtifactId is required");
var hashes = new Dictionary<string, string>();
if (_reachability?.Hash is not null) hashes["reachability"] = _reachability.Hash;

View File

@@ -0,0 +1,16 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>Status of an evidence artifact.</summary>
public enum EvidenceStatus
{
/// <summary>Evidence is available and complete.</summary>
Available,
/// <summary>Evidence is currently being loaded/computed.</summary>
Loading,
/// <summary>Evidence is not available (missing inputs).</summary>
Unavailable,
/// <summary>Error occurred while fetching evidence.</summary>
Error,
/// <summary>Evidence pending enrichment (offline mode).</summary>
PendingEnrichment
}

View File

@@ -0,0 +1,14 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>Graph revision and verdict receipt evidence.</summary>
public sealed class GraphRevisionEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public required string GraphRevisionId { get; init; }
public string? VerdictReceipt { get; init; }
public DateTimeOffset? GraphComputedAt { get; init; }
public int? TotalNodes { get; init; }
public int? TotalEdges { get; init; }
public string? UnavailableReason { get; init; }
}

View File

@@ -0,0 +1,44 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>Provenance attestation evidence.</summary>
public sealed class ProvenanceEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public DsseEnvelope? DsseEnvelope { get; init; }
public BuildAncestry? Ancestry { get; init; }
public RekorReference? RekorEntry { get; init; }
public string? VerificationStatus { get; init; }
public string? UnavailableReason { get; init; }
}
public sealed class DsseEnvelope
{
public required string PayloadType { get; init; }
public required string Payload { get; init; }
public required IReadOnlyList<DsseSignature> Signatures { get; init; }
}
public sealed class DsseSignature
{
public required string KeyId { get; init; }
public required string Sig { get; init; }
}
public sealed class BuildAncestry
{
public string? ImageDigest { get; init; }
public string? LayerDigest { get; init; }
public string? ArtifactDigest { get; init; }
public string? CommitHash { get; init; }
public string? BuildId { get; init; }
public DateTimeOffset? BuildTime { get; init; }
}
public sealed class RekorReference
{
public required string LogId { get; init; }
public required long LogIndex { get; init; }
public string? Uuid { get; init; }
public DateTimeOffset? IntegratedTime { get; init; }
}

View File

@@ -0,0 +1,33 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>Reachability proof evidence.</summary>
public sealed class ReachabilityEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public ReachabilityProofType ProofType { get; init; }
public IReadOnlyList<FunctionPathNode>? FunctionPath { get; init; }
public IReadOnlyList<PackageImportNode>? ImportChain { get; init; }
public string? LatticeState { get; init; }
public int? ConfidenceTier { get; init; }
public string? UnavailableReason { get; init; }
}
public enum ReachabilityProofType { FunctionLevel, PackageLevel, ImportChain, Heuristic, Unknown }
public sealed class FunctionPathNode
{
public required string FunctionName { get; init; }
public required string FilePath { get; init; }
public required int Line { get; init; }
public int? Column { get; init; }
public string? ModuleName { get; init; }
}
public sealed class PackageImportNode
{
public required string PackageName { get; init; }
public string? Version { get; init; }
public string? ImportedBy { get; init; }
public string? ImportPath { get; init; }
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<RootNamespace>StellaOps.Evidence.Bundle</RootNamespace>
<Description>Evidence bundle envelope with cryptographic signatures for offline verification</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-preview.1.25080.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
namespace StellaOps.Evidence.Bundle;
/// <summary>VEX/CSAF status evidence.</summary>
public sealed class VexStatusEvidence
{
public required EvidenceStatus Status { get; init; }
public string? Hash { get; init; }
public VexStatement? Current { get; init; }
public IReadOnlyList<VexStatement>? History { get; init; }
public string? UnavailableReason { get; init; }
}
public sealed class VexStatement
{
public required string VexStatus { get; init; }
public string? Justification { get; init; }
public string? ImpactStatement { get; init; }
public string? ActionStatement { get; init; }
public DateTimeOffset? Timestamp { get; init; }
public string? Source { get; init; }
}