feat: add Attestation Chain and Triage Evidence API clients and models

- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains.
- Created models for Attestation Chain, including DSSE envelope structures and verification results.
- Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component.
- Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence.
- Introduced mock implementations for both API clients to facilitate testing and development.
This commit is contained in:
master
2025-12-18 13:15:13 +02:00
parent 7d5250238c
commit 00d2c99af9
118 changed files with 13463 additions and 151 deletions

View File

@@ -0,0 +1,451 @@
// -----------------------------------------------------------------------------
// FindingEvidenceContracts.cs
// Sprint: SPRINT_3800_0001_0001_evidence_api_models
// Description: Unified evidence API response contracts for findings.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Unified evidence response for a finding, combining reachability, boundary,
/// VEX evidence, and score explanation.
/// </summary>
public sealed record FindingEvidenceResponse
{
/// <summary>
/// Unique identifier for the finding.
/// </summary>
[JsonPropertyName("finding_id")]
public string FindingId { get; init; } = string.Empty;
/// <summary>
/// CVE identifier (e.g., "CVE-2021-44228").
/// </summary>
[JsonPropertyName("cve")]
public string Cve { get; init; } = string.Empty;
/// <summary>
/// Component where the vulnerability was found.
/// </summary>
[JsonPropertyName("component")]
public ComponentRef? Component { get; init; }
/// <summary>
/// Reachable call path from entrypoint to vulnerable sink.
/// Each element is a fully-qualified name (FQN).
/// </summary>
[JsonPropertyName("reachable_path")]
public IReadOnlyList<string>? ReachablePath { get; init; }
/// <summary>
/// Entrypoint proof (how the code is exposed).
/// </summary>
[JsonPropertyName("entrypoint")]
public EntrypointProof? Entrypoint { get; init; }
/// <summary>
/// Boundary proof (surface exposure and controls).
/// </summary>
[JsonPropertyName("boundary")]
public BoundaryProofDto? Boundary { get; init; }
/// <summary>
/// VEX (Vulnerability Exploitability eXchange) evidence.
/// </summary>
[JsonPropertyName("vex")]
public VexEvidenceDto? Vex { get; init; }
/// <summary>
/// Score explanation with additive risk breakdown.
/// </summary>
[JsonPropertyName("score_explain")]
public ScoreExplanationDto? ScoreExplain { get; init; }
/// <summary>
/// When the finding was last observed.
/// </summary>
[JsonPropertyName("last_seen")]
public DateTimeOffset LastSeen { get; init; }
/// <summary>
/// When the evidence expires (for VEX/attestation freshness).
/// </summary>
[JsonPropertyName("expires_at")]
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>
/// References to DSSE/in-toto attestations backing this evidence.
/// </summary>
[JsonPropertyName("attestation_refs")]
public IReadOnlyList<string>? AttestationRefs { get; init; }
}
/// <summary>
/// Reference to a component (package) by PURL and version.
/// </summary>
public sealed record ComponentRef
{
/// <summary>
/// Package URL (PURL) identifier.
/// </summary>
[JsonPropertyName("purl")]
public string Purl { get; init; } = string.Empty;
/// <summary>
/// Package name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
/// <summary>
/// Package version.
/// </summary>
[JsonPropertyName("version")]
public string Version { get; init; } = string.Empty;
/// <summary>
/// Package type/ecosystem (npm, maven, nuget, etc.).
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
}
/// <summary>
/// Proof of how code is exposed as an entrypoint.
/// </summary>
public sealed record EntrypointProof
{
/// <summary>
/// Type of entrypoint (http_handler, grpc_method, cli_command, etc.).
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
/// <summary>
/// Route or path (e.g., "/api/v1/users", "grpc.UserService.GetUser").
/// </summary>
[JsonPropertyName("route")]
public string? Route { get; init; }
/// <summary>
/// HTTP method if applicable (GET, POST, etc.).
/// </summary>
[JsonPropertyName("method")]
public string? Method { get; init; }
/// <summary>
/// Authentication requirement (none, optional, required).
/// </summary>
[JsonPropertyName("auth")]
public string? Auth { get; init; }
/// <summary>
/// Execution phase (startup, runtime, shutdown).
/// </summary>
[JsonPropertyName("phase")]
public string? Phase { get; init; }
/// <summary>
/// Fully qualified name of the entrypoint symbol.
/// </summary>
[JsonPropertyName("fqn")]
public string Fqn { get; init; } = string.Empty;
/// <summary>
/// Source file location.
/// </summary>
[JsonPropertyName("location")]
public SourceLocation? Location { get; init; }
}
/// <summary>
/// Source file location reference.
/// </summary>
public sealed record SourceLocation
{
/// <summary>
/// File path relative to repository root.
/// </summary>
[JsonPropertyName("file")]
public string File { get; init; } = string.Empty;
/// <summary>
/// Line number (1-indexed).
/// </summary>
[JsonPropertyName("line")]
public int? Line { get; init; }
/// <summary>
/// Column number (1-indexed).
/// </summary>
[JsonPropertyName("column")]
public int? Column { get; init; }
}
/// <summary>
/// Boundary proof describing surface exposure and controls.
/// </summary>
public sealed record BoundaryProofDto
{
/// <summary>
/// Kind of boundary (network, file, ipc, etc.).
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = string.Empty;
/// <summary>
/// Surface descriptor (what is exposed).
/// </summary>
[JsonPropertyName("surface")]
public SurfaceDescriptor? Surface { get; init; }
/// <summary>
/// Exposure descriptor (how it's exposed).
/// </summary>
[JsonPropertyName("exposure")]
public ExposureDescriptor? Exposure { get; init; }
/// <summary>
/// Authentication descriptor.
/// </summary>
[JsonPropertyName("auth")]
public AuthDescriptor? Auth { get; init; }
/// <summary>
/// Security controls in place.
/// </summary>
[JsonPropertyName("controls")]
public IReadOnlyList<ControlDescriptor>? Controls { get; init; }
/// <summary>
/// When the boundary was last verified.
/// </summary>
[JsonPropertyName("last_seen")]
public DateTimeOffset LastSeen { get; init; }
/// <summary>
/// Confidence score (0.0 to 1.0).
/// </summary>
[JsonPropertyName("confidence")]
public double Confidence { get; init; }
}
/// <summary>
/// Describes what attack surface is exposed.
/// </summary>
public sealed record SurfaceDescriptor
{
/// <summary>
/// Type of surface (api, web, cli, library).
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
/// <summary>
/// Protocol (http, https, grpc, tcp).
/// </summary>
[JsonPropertyName("protocol")]
public string? Protocol { get; init; }
/// <summary>
/// Port number if network-exposed.
/// </summary>
[JsonPropertyName("port")]
public int? Port { get; init; }
}
/// <summary>
/// Describes how the surface is exposed.
/// </summary>
public sealed record ExposureDescriptor
{
/// <summary>
/// Exposure level (public, internal, private).
/// </summary>
[JsonPropertyName("level")]
public string Level { get; init; } = string.Empty;
/// <summary>
/// Whether the exposure is internet-facing.
/// </summary>
[JsonPropertyName("internet_facing")]
public bool InternetFacing { get; init; }
/// <summary>
/// Network zone (dmz, internal, trusted).
/// </summary>
[JsonPropertyName("zone")]
public string? Zone { get; init; }
}
/// <summary>
/// Describes authentication requirements.
/// </summary>
public sealed record AuthDescriptor
{
/// <summary>
/// Whether authentication is required.
/// </summary>
[JsonPropertyName("required")]
public bool Required { get; init; }
/// <summary>
/// Authentication type (jwt, oauth2, basic, api_key).
/// </summary>
[JsonPropertyName("type")]
public string? Type { get; init; }
/// <summary>
/// Required roles/scopes.
/// </summary>
[JsonPropertyName("roles")]
public IReadOnlyList<string>? Roles { get; init; }
}
/// <summary>
/// Describes a security control.
/// </summary>
public sealed record ControlDescriptor
{
/// <summary>
/// Type of control (rate_limit, waf, input_validation, etc.).
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
/// <summary>
/// Whether the control is active.
/// </summary>
[JsonPropertyName("active")]
public bool Active { get; init; }
/// <summary>
/// Control configuration details.
/// </summary>
[JsonPropertyName("config")]
public string? Config { get; init; }
}
/// <summary>
/// VEX (Vulnerability Exploitability eXchange) evidence.
/// </summary>
public sealed record VexEvidenceDto
{
/// <summary>
/// VEX status (not_affected, affected, fixed, under_investigation).
/// </summary>
[JsonPropertyName("status")]
public string Status { get; init; } = string.Empty;
/// <summary>
/// Justification for the status.
/// </summary>
[JsonPropertyName("justification")]
public string? Justification { get; init; }
/// <summary>
/// Impact statement explaining why not affected.
/// </summary>
[JsonPropertyName("impact")]
public string? Impact { get; init; }
/// <summary>
/// Action statement (remediation steps).
/// </summary>
[JsonPropertyName("action")]
public string? Action { get; init; }
/// <summary>
/// Reference to the VEX document/attestation.
/// </summary>
[JsonPropertyName("attestation_ref")]
public string? AttestationRef { get; init; }
/// <summary>
/// When the VEX statement was issued.
/// </summary>
[JsonPropertyName("issued_at")]
public DateTimeOffset? IssuedAt { get; init; }
/// <summary>
/// When the VEX statement expires.
/// </summary>
[JsonPropertyName("expires_at")]
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>
/// Source of the VEX statement (vendor, first-party, third-party).
/// </summary>
[JsonPropertyName("source")]
public string? Source { get; init; }
}
/// <summary>
/// Score explanation with additive breakdown of risk factors.
/// </summary>
public sealed record ScoreExplanationDto
{
/// <summary>
/// Kind of scoring algorithm (stellaops_risk_v1, cvss_v4, etc.).
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = string.Empty;
/// <summary>
/// Final computed risk score.
/// </summary>
[JsonPropertyName("risk_score")]
public double RiskScore { get; init; }
/// <summary>
/// Individual score contributions.
/// </summary>
[JsonPropertyName("contributions")]
public IReadOnlyList<ScoreContributionDto>? Contributions { get; init; }
/// <summary>
/// When the score was computed.
/// </summary>
[JsonPropertyName("last_seen")]
public DateTimeOffset LastSeen { get; init; }
}
/// <summary>
/// Individual contribution to the risk score.
/// </summary>
public sealed record ScoreContributionDto
{
/// <summary>
/// Factor name (cvss_base, epss, reachability, gate_multiplier, etc.).
/// </summary>
[JsonPropertyName("factor")]
public string Factor { get; init; } = string.Empty;
/// <summary>
/// Weight applied to this factor (0.0 to 1.0).
/// </summary>
[JsonPropertyName("weight")]
public double Weight { get; init; }
/// <summary>
/// Raw value before weighting.
/// </summary>
[JsonPropertyName("raw_value")]
public double RawValue { get; init; }
/// <summary>
/// Weighted contribution to final score.
/// </summary>
[JsonPropertyName("contribution")]
public double Contribution { get; init; }
/// <summary>
/// Human-readable explanation of this factor.
/// </summary>
[JsonPropertyName("explanation")]
public string? Explanation { get; init; }
}