save progress

This commit is contained in:
master
2026-01-09 18:27:36 +02:00
parent e608752924
commit a21d3dbc1f
361 changed files with 63068 additions and 1192 deletions

View File

@@ -0,0 +1,364 @@
// -----------------------------------------------------------------------------
// BackportContracts.cs
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
// Task: DR-014 — API contracts for backport evidence endpoints
// -----------------------------------------------------------------------------
namespace StellaOps.Findings.Ledger.WebService.Contracts;
/// <summary>
/// Response containing backport verification evidence for a finding.
/// </summary>
public sealed record BackportEvidenceResponse
{
/// <summary>
/// Finding this evidence is for.
/// </summary>
public required Guid FindingId { get; init; }
/// <summary>
/// Backport verification verdict.
/// </summary>
public required BackportVerdict Verdict { get; init; }
/// <summary>
/// Diff content for each patch.
/// </summary>
public IReadOnlyList<DiffContent>? Diffs { get; init; }
}
/// <summary>
/// Backport verification verdict from Feedser.
/// </summary>
public sealed record BackportVerdict
{
/// <summary>
/// Verification status.
/// </summary>
public required BackportVerdictStatus Status { get; init; }
/// <summary>
/// Confidence score (0.0 to 1.0).
/// </summary>
public required double Confidence { get; init; }
/// <summary>
/// Evidence tier (1-5).
/// </summary>
public required EvidenceTier Tier { get; init; }
/// <summary>
/// Human-readable tier description.
/// </summary>
public required string TierDescription { get; init; }
/// <summary>
/// Upstream package information.
/// </summary>
public required UpstreamInfo Upstream { get; init; }
/// <summary>
/// Distro package information.
/// </summary>
public required DistroInfo Distro { get; init; }
/// <summary>
/// Patch signatures that verify the backport.
/// </summary>
public required IReadOnlyList<PatchSignature> Patches { get; init; }
}
/// <summary>
/// Verification status enumeration.
/// </summary>
public enum BackportVerdictStatus
{
/// <summary>
/// Backport verified with high confidence.
/// </summary>
Verified,
/// <summary>
/// Backport could not be verified.
/// </summary>
Unverified,
/// <summary>
/// Unable to determine backport status.
/// </summary>
Unknown,
/// <summary>
/// Partially verified (some patches confirmed).
/// </summary>
Partial
}
/// <summary>
/// Evidence tier classification.
/// </summary>
public enum EvidenceTier
{
/// <summary>
/// Tier 1: Confirmed by distro advisory (95-100% confidence).
/// </summary>
DistroAdvisory = 1,
/// <summary>
/// Tier 2: Confirmed by changelog (80-94% confidence).
/// </summary>
Changelog = 2,
/// <summary>
/// Tier 3: Patch header match (65-79% confidence).
/// </summary>
PatchHeader = 3,
/// <summary>
/// Tier 4: Binary fingerprint match (40-64% confidence).
/// </summary>
BinaryFingerprint = 4,
/// <summary>
/// Tier 5: NVD heuristic match (20-39% confidence).
/// </summary>
NvdHeuristic = 5
}
/// <summary>
/// Upstream package information.
/// </summary>
public sealed record UpstreamInfo
{
/// <summary>
/// Package URL (purl).
/// </summary>
public required string Purl { get; init; }
/// <summary>
/// Commit SHA that fixes the vulnerability.
/// </summary>
public string? CommitSha { get; init; }
/// <summary>
/// URL to the commit.
/// </summary>
public string? CommitUrl { get; init; }
/// <summary>
/// CVEs resolved by this upstream version.
/// </summary>
public IReadOnlyList<string>? Resolves { get; init; }
}
/// <summary>
/// Distribution package information.
/// </summary>
public sealed record DistroInfo
{
/// <summary>
/// Package URL (purl).
/// </summary>
public required string Purl { get; init; }
/// <summary>
/// Advisory ID (e.g., DSA-5678).
/// </summary>
public string? AdvisoryId { get; init; }
/// <summary>
/// Advisory URL.
/// </summary>
public string? AdvisoryUrl { get; init; }
}
/// <summary>
/// Patch signature information.
/// </summary>
public sealed record PatchSignature
{
/// <summary>
/// Signature identifier.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Type of patch.
/// </summary>
public required string Type { get; init; }
/// <summary>
/// File path being patched.
/// </summary>
public required string FilePath { get; init; }
/// <summary>
/// Hunk signature (content-addressed).
/// </summary>
public required string HunkSignature { get; init; }
/// <summary>
/// CVEs resolved by this patch.
/// </summary>
public IReadOnlyList<string>? Resolves { get; init; }
/// <summary>
/// Whether this is the primary patch.
/// </summary>
public bool IsPrimary { get; init; }
/// <summary>
/// URL to fetch diff content.
/// </summary>
public string? DiffUrl { get; init; }
}
/// <summary>
/// Diff content for a patch.
/// </summary>
public sealed record DiffContent
{
/// <summary>
/// Signature ID this diff is for.
/// </summary>
public required string SignatureId { get; init; }
/// <summary>
/// Original file path.
/// </summary>
public required string OldPath { get; init; }
/// <summary>
/// New file path.
/// </summary>
public required string NewPath { get; init; }
/// <summary>
/// Raw unified diff content.
/// </summary>
public string? RawDiff { get; init; }
/// <summary>
/// Parsed hunks.
/// </summary>
public IReadOnlyList<DiffHunk>? Hunks { get; init; }
/// <summary>
/// Number of additions.
/// </summary>
public int Additions { get; init; }
/// <summary>
/// Number of deletions.
/// </summary>
public int Deletions { get; init; }
}
/// <summary>
/// A hunk in a unified diff.
/// </summary>
public sealed record DiffHunk
{
/// <summary>
/// Hunk index.
/// </summary>
public required int Index { get; init; }
/// <summary>
/// Start line in old file.
/// </summary>
public required int OldStart { get; init; }
/// <summary>
/// Line count in old file.
/// </summary>
public required int OldCount { get; init; }
/// <summary>
/// Start line in new file.
/// </summary>
public required int NewStart { get; init; }
/// <summary>
/// Line count in new file.
/// </summary>
public required int NewCount { get; init; }
/// <summary>
/// Hunk header (@@...@@).
/// </summary>
public required string Header { get; init; }
/// <summary>
/// Function context.
/// </summary>
public string? FunctionContext { get; init; }
/// <summary>
/// Lines in this hunk.
/// </summary>
public required IReadOnlyList<DiffLine> Lines { get; init; }
}
/// <summary>
/// A line in a diff hunk.
/// </summary>
public sealed record DiffLine
{
/// <summary>
/// Line type.
/// </summary>
public required DiffLineType Type { get; init; }
/// <summary>
/// Line content.
/// </summary>
public required string Content { get; init; }
/// <summary>
/// Line number in old file.
/// </summary>
public int? OldLineNumber { get; init; }
/// <summary>
/// Line number in new file.
/// </summary>
public int? NewLineNumber { get; init; }
}
/// <summary>
/// Type of diff line.
/// </summary>
public enum DiffLineType
{
/// <summary>
/// Context line (unchanged).
/// </summary>
Context,
/// <summary>
/// Added line.
/// </summary>
Addition,
/// <summary>
/// Deleted line.
/// </summary>
Deletion
}
/// <summary>
/// Response containing patches for a finding.
/// </summary>
public sealed record PatchesResponse
{
/// <summary>
/// Finding ID.
/// </summary>
public required Guid FindingId { get; init; }
/// <summary>
/// Patches that affect this finding.
/// </summary>
public required IReadOnlyList<PatchSignature> Patches { get; init; }
}

View File

@@ -0,0 +1,255 @@
// -----------------------------------------------------------------------------
// RuntimeTracesContracts.cs
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
// Task: DR-014 — API contracts for runtime traces endpoints
// -----------------------------------------------------------------------------
namespace StellaOps.Findings.Ledger.WebService.Contracts;
/// <summary>
/// Response containing runtime traces for a finding.
/// </summary>
public sealed record RuntimeTracesResponse
{
/// <summary>
/// Finding this evidence is for.
/// </summary>
public required Guid FindingId { get; init; }
/// <summary>
/// Whether collection is currently active.
/// </summary>
public required bool CollectionActive { get; init; }
/// <summary>
/// When collection started.
/// </summary>
public DateTimeOffset? CollectionStarted { get; init; }
/// <summary>
/// Summary of observations.
/// </summary>
public required ObservationSummary Summary { get; init; }
/// <summary>
/// Function traces.
/// </summary>
public required IReadOnlyList<FunctionTrace> Traces { get; init; }
}
/// <summary>
/// Summary of runtime observations.
/// </summary>
public sealed record ObservationSummary
{
/// <summary>
/// Total hit count across all traces.
/// </summary>
public required long TotalHits { get; init; }
/// <summary>
/// Number of unique call paths.
/// </summary>
public required int UniquePaths { get; init; }
/// <summary>
/// Observation posture.
/// </summary>
public required RuntimePosture Posture { get; init; }
/// <summary>
/// Last hit timestamp.
/// </summary>
public DateTimeOffset? LastHit { get; init; }
/// <summary>
/// Whether a direct path to vulnerable function was observed.
/// </summary>
public required bool DirectPathObserved { get; init; }
/// <summary>
/// Whether production traffic was observed.
/// </summary>
public required bool ProductionTraffic { get; init; }
/// <summary>
/// Number of containers with observations.
/// </summary>
public required int ContainerCount { get; init; }
}
/// <summary>
/// Runtime observation posture.
/// </summary>
public enum RuntimePosture
{
/// <summary>
/// No runtime observation configured.
/// </summary>
None = 0,
/// <summary>
/// Passive observation (logs only).
/// </summary>
Passive = 1,
/// <summary>
/// Active tracing (syscalls/ETW).
/// </summary>
ActiveTracing = 2,
/// <summary>
/// eBPF deep probes active.
/// </summary>
EbpfDeep = 3,
/// <summary>
/// Full instrumentation coverage.
/// </summary>
FullInstrumentation = 4
}
/// <summary>
/// A function trace showing a call path to a vulnerable function.
/// </summary>
public sealed record FunctionTrace
{
/// <summary>
/// Trace identifier.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Vulnerable function symbol.
/// </summary>
public required string VulnerableFunction { get; init; }
/// <summary>
/// Whether this is a direct path.
/// </summary>
public required bool IsDirectPath { get; init; }
/// <summary>
/// Number of times this path was hit.
/// </summary>
public required long HitCount { get; init; }
/// <summary>
/// First observation timestamp.
/// </summary>
public required DateTimeOffset FirstSeen { get; init; }
/// <summary>
/// Last observation timestamp.
/// </summary>
public required DateTimeOffset LastSeen { get; init; }
/// <summary>
/// Container ID where observed.
/// </summary>
public string? ContainerId { get; init; }
/// <summary>
/// Container name.
/// </summary>
public string? ContainerName { get; init; }
/// <summary>
/// Call path (stack frames).
/// </summary>
public required IReadOnlyList<StackFrame> CallPath { get; init; }
}
/// <summary>
/// A stack frame in a call path.
/// </summary>
public sealed record StackFrame
{
/// <summary>
/// Function/method symbol.
/// </summary>
public required string Symbol { get; init; }
/// <summary>
/// Source file path.
/// </summary>
public string? File { get; init; }
/// <summary>
/// Line number.
/// </summary>
public int? Line { get; init; }
/// <summary>
/// Whether this is an entry point.
/// </summary>
public bool IsEntryPoint { get; init; }
/// <summary>
/// Whether this is the vulnerable function.
/// </summary>
public bool IsVulnerableFunction { get; init; }
/// <summary>
/// Confidence score for this frame.
/// </summary>
public double? Confidence { get; init; }
}
/// <summary>
/// Response containing RTS score for a finding.
/// </summary>
public sealed record RtsScoreResponse
{
/// <summary>
/// Finding ID.
/// </summary>
public required Guid FindingId { get; init; }
/// <summary>
/// RTS score.
/// </summary>
public required RtsScore Score { get; init; }
}
/// <summary>
/// Runtime Trustworthiness Score.
/// </summary>
public sealed record RtsScore
{
/// <summary>
/// Aggregate score (0.0 to 1.0).
/// </summary>
public required double Score { get; init; }
/// <summary>
/// Score breakdown.
/// </summary>
public required RtsBreakdown Breakdown { get; init; }
/// <summary>
/// When the score was computed.
/// </summary>
public required DateTimeOffset ComputedAt { get; init; }
}
/// <summary>
/// Breakdown of RTS score components.
/// </summary>
public sealed record RtsBreakdown
{
/// <summary>
/// Score based on observation quality (0.0 to 1.0).
/// </summary>
public required double ObservationScore { get; init; }
/// <summary>
/// Factor based on recency of observations (0.0 to 1.0).
/// </summary>
public required double RecencyFactor { get; init; }
/// <summary>
/// Factor based on data quality (0.0 to 1.0).
/// </summary>
public required double QualityFactor { get; init; }
}

View File

@@ -0,0 +1,92 @@
// -----------------------------------------------------------------------------
// BackportEndpoints.cs
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
// Task: DR-014 — Backport evidence API endpoints
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.HttpResults;
using StellaOps.Findings.Ledger.WebService.Contracts;
namespace StellaOps.Findings.Ledger.WebService.Endpoints;
/// <summary>
/// API endpoints for backport verification evidence.
/// </summary>
public static class BackportEndpoints
{
/// <summary>
/// Maps backport endpoints to the application.
/// </summary>
public static void MapBackportEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/v1/findings")
.WithTags("Backport Evidence")
.RequireAuthorization();
// GET /api/v1/findings/{findingId}/backport
group.MapGet("/{findingId:guid}/backport", GetBackportEvidence)
.WithName("GetBackportEvidence")
.WithDescription("Get backport verification evidence for a finding")
.Produces<BackportEvidenceResponse>(200)
.Produces(404);
// GET /api/v1/findings/{findingId}/patches
group.MapGet("/{findingId:guid}/patches", GetPatches)
.WithName("GetPatches")
.WithDescription("Get patch signatures for a finding")
.Produces<PatchesResponse>(200)
.Produces(404);
}
/// <summary>
/// Gets backport verification evidence for a finding.
/// </summary>
private static async Task<Results<Ok<BackportEvidenceResponse>, NotFound>> GetBackportEvidence(
Guid findingId,
IBackportEvidenceService service,
CancellationToken ct)
{
var evidence = await service.GetBackportEvidenceAsync(findingId, ct);
return evidence is not null
? TypedResults.Ok(evidence)
: TypedResults.NotFound();
}
/// <summary>
/// Gets patch signatures for a finding.
/// </summary>
private static async Task<Results<Ok<PatchesResponse>, NotFound>> GetPatches(
Guid findingId,
IBackportEvidenceService service,
CancellationToken ct)
{
var patches = await service.GetPatchesAsync(findingId, ct);
return patches is not null
? TypedResults.Ok(patches)
: TypedResults.NotFound();
}
}
/// <summary>
/// Service for retrieving backport evidence from Feedser.
/// </summary>
public interface IBackportEvidenceService
{
/// <summary>
/// Gets backport verification evidence for a finding.
/// </summary>
/// <param name="findingId">Finding identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Backport evidence response or null if not found.</returns>
Task<BackportEvidenceResponse?> GetBackportEvidenceAsync(Guid findingId, CancellationToken ct);
/// <summary>
/// Gets patch signatures for a finding.
/// </summary>
/// <param name="findingId">Finding identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Patches response or null if not found.</returns>
Task<PatchesResponse?> GetPatchesAsync(Guid findingId, CancellationToken ct);
}

View File

@@ -0,0 +1,121 @@
// -----------------------------------------------------------------------------
// RuntimeTracesEndpoints.cs
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
// Task: DR-014 — Runtime traces API endpoints
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Findings.Ledger.WebService.Contracts;
namespace StellaOps.Findings.Ledger.WebService.Endpoints;
/// <summary>
/// API endpoints for runtime traces evidence.
/// </summary>
public static class RuntimeTracesEndpoints
{
/// <summary>
/// Maps runtime traces endpoints to the application.
/// </summary>
public static void MapRuntimeTracesEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/v1/findings")
.WithTags("Runtime Evidence")
.RequireAuthorization();
// GET /api/v1/findings/{findingId}/runtime/traces
group.MapGet("/{findingId:guid}/runtime/traces", GetRuntimeTraces)
.WithName("GetRuntimeTraces")
.WithDescription("Get runtime function traces for a finding")
.Produces<RuntimeTracesResponse>(200)
.Produces(404);
// GET /api/v1/findings/{findingId}/runtime/score
group.MapGet("/{findingId:guid}/runtime/score", GetRtsScore)
.WithName("GetRtsScore")
.WithDescription("Get Runtime Trustworthiness Score for a finding")
.Produces<RtsScoreResponse>(200)
.Produces(404);
}
/// <summary>
/// Gets runtime function traces for a finding.
/// </summary>
private static async Task<Results<Ok<RuntimeTracesResponse>, NotFound>> GetRuntimeTraces(
Guid findingId,
IRuntimeTracesService service,
CancellationToken ct,
[FromQuery] int? limit = null,
[FromQuery] string? sortBy = null)
{
var options = new RuntimeTracesQueryOptions
{
Limit = limit ?? 50,
SortBy = sortBy ?? "hits"
};
var traces = await service.GetTracesAsync(findingId, options, ct);
return traces is not null
? TypedResults.Ok(traces)
: TypedResults.NotFound();
}
/// <summary>
/// Gets the RTS score for a finding.
/// </summary>
private static async Task<Results<Ok<RtsScoreResponse>, NotFound>> GetRtsScore(
Guid findingId,
IRuntimeTracesService service,
CancellationToken ct)
{
var score = await service.GetRtsScoreAsync(findingId, ct);
return score is not null
? TypedResults.Ok(score)
: TypedResults.NotFound();
}
}
/// <summary>
/// Query options for runtime traces.
/// </summary>
public sealed record RuntimeTracesQueryOptions
{
/// <summary>
/// Maximum number of traces to return.
/// </summary>
public int Limit { get; init; } = 50;
/// <summary>
/// Sort by field (hits, recent).
/// </summary>
public string SortBy { get; init; } = "hits";
}
/// <summary>
/// Service for retrieving runtime traces.
/// </summary>
public interface IRuntimeTracesService
{
/// <summary>
/// Gets runtime traces for a finding.
/// </summary>
/// <param name="findingId">Finding identifier.</param>
/// <param name="options">Query options.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Runtime traces response or null if not found.</returns>
Task<RuntimeTracesResponse?> GetTracesAsync(
Guid findingId,
RuntimeTracesQueryOptions options,
CancellationToken ct);
/// <summary>
/// Gets RTS score for a finding.
/// </summary>
/// <param name="findingId">Finding identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>RTS score response or null if not found.</returns>
Task<RtsScoreResponse?> GetRtsScoreAsync(Guid findingId, CancellationToken ct);
}

View File

@@ -1926,6 +1926,10 @@ app.MapEvidenceGraphEndpoints();
app.MapReachabilityMapEndpoints();
app.MapRuntimeTimelineEndpoints();
// Backport and runtime traces endpoints (SPRINT_20260107_006_002_FE)
app.MapBackportEndpoints();
app.MapRuntimeTracesEndpoints();
// Map EWS scoring and webhook endpoints (SPRINT_8200.0012.0004)
app.MapScoringEndpoints();
app.MapWebhookEndpoints();