Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,514 @@
// <copyright file="EvidenceSubgraphContracts.cs" company="StellaOps">
// SPDX-License-Identifier: AGPL-3.0-or-later
// </copyright>
namespace StellaOps.VulnExplorer.WebService.Contracts;
using System.Text.Json.Serialization;
/// <summary>
/// Response containing the evidence subgraph for a finding.
/// </summary>
public sealed record EvidenceSubgraphResponse
{
/// <summary>
/// Finding identifier this subgraph explains.
/// </summary>
public required string FindingId { get; init; }
/// <summary>
/// Vulnerability identifier (CVE ID or similar).
/// </summary>
public required string VulnId { get; init; }
/// <summary>
/// Root node of the evidence graph (typically the artifact).
/// </summary>
public required EvidenceNode Root { get; init; }
/// <summary>
/// All edges in the evidence graph.
/// </summary>
public required IReadOnlyList<EvidenceEdge> Edges { get; init; }
/// <summary>
/// Summary verdict for this finding.
/// </summary>
public required VerdictSummary Verdict { get; init; }
/// <summary>
/// Available triage actions for this finding.
/// </summary>
public required IReadOnlyList<TriageAction> AvailableActions { get; init; }
/// <summary>
/// Optional metadata about the evidence collection.
/// </summary>
public EvidenceMetadata? Metadata { get; init; }
}
/// <summary>
/// Node in the evidence graph.
/// </summary>
public sealed record EvidenceNode
{
/// <summary>
/// Unique identifier for this node.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Type of evidence this node represents.
/// </summary>
public required EvidenceNodeType Type { get; init; }
/// <summary>
/// Human-readable label for display.
/// </summary>
public required string Label { get; init; }
/// <summary>
/// Optional longer description.
/// </summary>
public string? Description { get; init; }
/// <summary>
/// Type-specific metadata.
/// </summary>
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
/// <summary>
/// Child nodes (for expandable tree view).
/// </summary>
public IReadOnlyList<EvidenceNode>? Children { get; init; }
/// <summary>
/// Whether this node is expandable (has or can load children).
/// </summary>
public bool IsExpandable { get; init; }
/// <summary>
/// Status indicator for this node (pass/fail/info).
/// </summary>
public EvidenceNodeStatus Status { get; init; } = EvidenceNodeStatus.Info;
}
/// <summary>
/// Type of evidence node.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum EvidenceNodeType
{
/// <summary>Container image or artifact.</summary>
Artifact,
/// <summary>Software package (PURL).</summary>
Package,
/// <summary>Code symbol (function, class, method).</summary>
Symbol,
/// <summary>Call path from entry point to vulnerable code.</summary>
CallPath,
/// <summary>VEX claim from vendor or internal team.</summary>
VexClaim,
/// <summary>Policy rule that affected the verdict.</summary>
PolicyRule,
/// <summary>External advisory source (NVD, vendor, etc.).</summary>
AdvisorySource,
/// <summary>Scanner evidence (binary analysis, SBOM, etc.).</summary>
ScannerEvidence,
/// <summary>Runtime observation (eBPF, traces).</summary>
RuntimeObservation,
/// <summary>Configuration or environment context.</summary>
Configuration,
}
/// <summary>
/// Status indicator for evidence nodes.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum EvidenceNodeStatus
{
/// <summary>Informational, no pass/fail.</summary>
Info,
/// <summary>Positive indicator (mitigating factor).</summary>
Pass,
/// <summary>Negative indicator (risk factor).</summary>
Fail,
/// <summary>Needs review or additional information.</summary>
Warning,
/// <summary>Unknown or incomplete data.</summary>
Unknown,
}
/// <summary>
/// Edge connecting two evidence nodes.
/// </summary>
public sealed record EvidenceEdge
{
/// <summary>
/// Source node identifier.
/// </summary>
public required string SourceId { get; init; }
/// <summary>
/// Target node identifier.
/// </summary>
public required string TargetId { get; init; }
/// <summary>
/// Type of relationship (contains, calls, claims, references, etc.).
/// </summary>
public required string Relationship { get; init; }
/// <summary>
/// Citation linking to source evidence.
/// </summary>
public required EvidenceCitation Citation { get; init; }
/// <summary>
/// Whether this edge represents a reachable path.
/// </summary>
public bool IsReachable { get; init; }
/// <summary>
/// Strength of the relationship (for visualization).
/// </summary>
public double? Weight { get; init; }
}
/// <summary>
/// Citation linking to source evidence.
/// </summary>
public sealed record EvidenceCitation
{
/// <summary>
/// Source type (scanner, vex:vendor, advisory:nvd, etc.).
/// </summary>
public required string Source { get; init; }
/// <summary>
/// URL to the source evidence (if available).
/// </summary>
public required string SourceUrl { get; init; }
/// <summary>
/// When this evidence was observed/collected.
/// </summary>
public required DateTimeOffset ObservedAt { get; init; }
/// <summary>
/// Confidence score (0.0-1.0) if applicable.
/// </summary>
public double? Confidence { get; init; }
/// <summary>
/// Hash of the evidence (for verification).
/// </summary>
public string? EvidenceHash { get; init; }
/// <summary>
/// Whether this citation is verified/signed.
/// </summary>
public bool IsVerified { get; init; }
}
/// <summary>
/// Summary verdict for a finding.
/// </summary>
public sealed record VerdictSummary
{
/// <summary>
/// Decision outcome (allow, deny, review).
/// </summary>
public required string Decision { get; init; }
/// <summary>
/// Human-readable explanation paragraph.
/// </summary>
public required string Explanation { get; init; }
/// <summary>
/// Key factors that influenced the decision.
/// </summary>
public required IReadOnlyList<string> KeyFactors { get; init; }
/// <summary>
/// Overall confidence score (0.0-1.0).
/// </summary>
public required double ConfidenceScore { get; init; }
/// <summary>
/// Policy rule IDs that affected this verdict.
/// </summary>
public IReadOnlyList<string>? AppliedPolicies { get; init; }
/// <summary>
/// Timestamp when this verdict was computed.
/// </summary>
public DateTimeOffset? ComputedAt { get; init; }
}
/// <summary>
/// Available triage action.
/// </summary>
public sealed record TriageAction
{
/// <summary>
/// Unique action identifier.
/// </summary>
public required string ActionId { get; init; }
/// <summary>
/// Type of action.
/// </summary>
public required TriageActionType Type { get; init; }
/// <summary>
/// Display label.
/// </summary>
public required string Label { get; init; }
/// <summary>
/// Optional description of what this action does.
/// </summary>
public string? Description { get; init; }
/// <summary>
/// Whether this action requires confirmation dialog.
/// </summary>
public bool RequiresConfirmation { get; init; }
/// <summary>
/// Whether this action is currently available.
/// </summary>
public bool IsEnabled { get; init; } = true;
/// <summary>
/// Reason if the action is disabled.
/// </summary>
public string? DisabledReason { get; init; }
/// <summary>
/// Additional parameters for the action.
/// </summary>
public IReadOnlyDictionary<string, object>? Parameters { get; init; }
}
/// <summary>
/// Type of triage action.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum TriageActionType
{
/// <summary>Accept vendor's VEX claim.</summary>
AcceptVendorVex,
/// <summary>Request additional evidence.</summary>
RequestEvidence,
/// <summary>Open diff view to previous version.</summary>
OpenDiff,
/// <summary>Create time-boxed policy exception.</summary>
CreateException,
/// <summary>Mark as false positive.</summary>
MarkFalsePositive,
/// <summary>Escalate to security team.</summary>
EscalateToSecurityTeam,
/// <summary>Apply internal VEX claim.</summary>
ApplyInternalVex,
/// <summary>Schedule for patching.</summary>
SchedulePatch,
/// <summary>Suppress finding.</summary>
Suppress,
}
/// <summary>
/// Metadata about evidence collection.
/// </summary>
public sealed record EvidenceMetadata
{
/// <summary>
/// When the evidence was collected.
/// </summary>
public DateTimeOffset CollectedAt { get; init; }
/// <summary>
/// Number of nodes in the graph.
/// </summary>
public int NodeCount { get; init; }
/// <summary>
/// Number of edges in the graph.
/// </summary>
public int EdgeCount { get; init; }
/// <summary>
/// Whether the graph is complete or truncated.
/// </summary>
public bool IsTruncated { get; init; }
/// <summary>
/// Maximum depth of the tree.
/// </summary>
public int MaxDepth { get; init; }
/// <summary>
/// Sources of evidence included.
/// </summary>
public IReadOnlyList<string>? Sources { get; init; }
}
/// <summary>
/// Request to execute a triage action.
/// </summary>
public sealed record ExecuteTriageActionRequest
{
/// <summary>
/// Finding to apply action to.
/// </summary>
public required string FindingId { get; init; }
/// <summary>
/// Action to execute.
/// </summary>
public required string ActionId { get; init; }
/// <summary>
/// Optional parameters for the action.
/// </summary>
public IReadOnlyDictionary<string, object>? Parameters { get; init; }
/// <summary>
/// User comment/justification.
/// </summary>
public string? Comment { get; init; }
}
/// <summary>
/// Response from triage action execution.
/// </summary>
public sealed record ExecuteTriageActionResponse
{
/// <summary>
/// Whether the action succeeded.
/// </summary>
public required bool Success { get; init; }
/// <summary>
/// Result message.
/// </summary>
public required string Message { get; init; }
/// <summary>
/// Updated verdict after action (if changed).
/// </summary>
public VerdictSummary? UpdatedVerdict { get; init; }
/// <summary>
/// Next recommended action (if any).
/// </summary>
public TriageAction? NextAction { get; init; }
}
/// <summary>
/// Filters for finding triage.
/// </summary>
public sealed record TriageFilters
{
/// <summary>
/// Reachability filter.
/// </summary>
public ReachabilityFilter Reachability { get; init; } = ReachabilityFilter.Reachable;
/// <summary>
/// Patch status filter.
/// </summary>
public PatchStatusFilter PatchStatus { get; init; } = PatchStatusFilter.Unpatched;
/// <summary>
/// VEX status filter.
/// </summary>
public VexStatusFilter VexStatus { get; init; } = VexStatusFilter.Unvexed;
/// <summary>
/// Severity levels to include.
/// </summary>
public IReadOnlyList<string> Severity { get; init; } = new[] { "critical", "high" };
/// <summary>
/// Whether to show suppressed findings.
/// </summary>
public bool ShowSuppressed { get; init; }
}
/// <summary>
/// Reachability filter options.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ReachabilityFilter
{
/// <summary>Show all findings.</summary>
All,
/// <summary>Show only reachable findings.</summary>
Reachable,
/// <summary>Show only unreachable findings.</summary>
Unreachable,
/// <summary>Show findings with unknown reachability.</summary>
Unknown,
}
/// <summary>
/// Patch status filter options.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PatchStatusFilter
{
/// <summary>Show all findings.</summary>
All,
/// <summary>Show only findings with available patches.</summary>
Patched,
/// <summary>Show only findings without available patches.</summary>
Unpatched,
}
/// <summary>
/// VEX status filter options.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum VexStatusFilter
{
/// <summary>Show all findings.</summary>
All,
/// <summary>Show only findings with VEX claims.</summary>
Vexed,
/// <summary>Show only findings without VEX claims.</summary>
Unvexed,
/// <summary>Show findings with conflicting VEX claims.</summary>
Conflicting,
}