sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -0,0 +1,141 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Response for GET /scans/{scanId}/layers endpoint.
/// </summary>
public sealed record LayerListResponseDto
{
[JsonPropertyName("scanId")]
public required string ScanId { get; init; }
[JsonPropertyName("imageDigest")]
public required string ImageDigest { get; init; }
[JsonPropertyName("layers")]
public required IReadOnlyList<LayerSummaryDto> Layers { get; init; }
}
/// <summary>
/// Summary of a single layer.
/// </summary>
public sealed record LayerSummaryDto
{
[JsonPropertyName("digest")]
public required string Digest { get; init; }
[JsonPropertyName("order")]
public required int Order { get; init; }
[JsonPropertyName("hasSbom")]
public required bool HasSbom { get; init; }
[JsonPropertyName("componentCount")]
public required int ComponentCount { get; init; }
}
/// <summary>
/// Response for GET /scans/{scanId}/composition-recipe endpoint.
/// </summary>
public sealed record CompositionRecipeResponseDto
{
[JsonPropertyName("scanId")]
public required string ScanId { get; init; }
[JsonPropertyName("imageDigest")]
public required string ImageDigest { get; init; }
[JsonPropertyName("createdAt")]
public required string CreatedAt { get; init; }
[JsonPropertyName("recipe")]
public required CompositionRecipeDto Recipe { get; init; }
}
/// <summary>
/// The composition recipe.
/// </summary>
public sealed record CompositionRecipeDto
{
[JsonPropertyName("version")]
public required string Version { get; init; }
[JsonPropertyName("generatorName")]
public required string GeneratorName { get; init; }
[JsonPropertyName("generatorVersion")]
public required string GeneratorVersion { get; init; }
[JsonPropertyName("layers")]
public required IReadOnlyList<CompositionRecipeLayerDto> Layers { get; init; }
[JsonPropertyName("merkleRoot")]
public required string MerkleRoot { get; init; }
[JsonPropertyName("aggregatedSbomDigests")]
public required AggregatedSbomDigestsDto AggregatedSbomDigests { get; init; }
}
/// <summary>
/// A layer in the composition recipe.
/// </summary>
public sealed record CompositionRecipeLayerDto
{
[JsonPropertyName("digest")]
public required string Digest { get; init; }
[JsonPropertyName("order")]
public required int Order { get; init; }
[JsonPropertyName("fragmentDigest")]
public required string FragmentDigest { get; init; }
[JsonPropertyName("sbomDigests")]
public required LayerSbomDigestsDto SbomDigests { get; init; }
[JsonPropertyName("componentCount")]
public required int ComponentCount { get; init; }
}
/// <summary>
/// Digests for a layer's SBOMs.
/// </summary>
public sealed record LayerSbomDigestsDto
{
[JsonPropertyName("cyclonedx")]
public required string CycloneDx { get; init; }
[JsonPropertyName("spdx")]
public required string Spdx { get; init; }
}
/// <summary>
/// Digests for aggregated SBOMs.
/// </summary>
public sealed record AggregatedSbomDigestsDto
{
[JsonPropertyName("cyclonedx")]
public required string CycloneDx { get; init; }
[JsonPropertyName("spdx")]
public string? Spdx { get; init; }
}
/// <summary>
/// Result of composition recipe verification.
/// </summary>
public sealed record CompositionRecipeVerificationResponseDto
{
[JsonPropertyName("valid")]
public required bool Valid { get; init; }
[JsonPropertyName("merkleRootMatch")]
public required bool MerkleRootMatch { get; init; }
[JsonPropertyName("layerDigestsMatch")]
public required bool LayerDigestsMatch { get; init; }
[JsonPropertyName("errors")]
public IReadOnlyList<string>? Errors { get; init; }
}

View File

@@ -243,6 +243,71 @@ internal sealed record ScanCompletedEventPayload : OrchestratorEventPayload
[JsonPropertyName("report")]
[JsonPropertyOrder(10)]
public ReportDocumentDto Report { get; init; } = new();
/// <summary>
/// VEX gate evaluation summary (Sprint: SPRINT_20260106_003_002, Task: T024).
/// </summary>
[JsonPropertyName("vexGate")]
[JsonPropertyOrder(11)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public VexGateSummaryPayload? VexGate { get; init; }
}
/// <summary>
/// VEX gate evaluation summary for scan completion events.
/// Sprint: SPRINT_20260106_003_002_SCANNER_vex_gate_service, Task: T024
/// </summary>
internal sealed record VexGateSummaryPayload
{
/// <summary>
/// Total findings evaluated by the gate.
/// </summary>
[JsonPropertyName("totalFindings")]
[JsonPropertyOrder(0)]
public int TotalFindings { get; init; }
/// <summary>
/// Findings that passed (cleared by VEX evidence).
/// </summary>
[JsonPropertyName("passed")]
[JsonPropertyOrder(1)]
public int Passed { get; init; }
/// <summary>
/// Findings with warnings (partial evidence).
/// </summary>
[JsonPropertyName("warned")]
[JsonPropertyOrder(2)]
public int Warned { get; init; }
/// <summary>
/// Findings that were blocked (require attention).
/// </summary>
[JsonPropertyName("blocked")]
[JsonPropertyOrder(3)]
public int Blocked { get; init; }
/// <summary>
/// Whether the gate was bypassed for this scan.
/// </summary>
[JsonPropertyName("bypassed")]
[JsonPropertyOrder(4)]
public bool Bypassed { get; init; }
/// <summary>
/// Policy version used for evaluation.
/// </summary>
[JsonPropertyName("policyVersion")]
[JsonPropertyOrder(5)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? PolicyVersion { get; init; }
/// <summary>
/// When the gate evaluation was performed.
/// </summary>
[JsonPropertyName("evaluatedAt")]
[JsonPropertyOrder(6)]
public DateTimeOffset EvaluatedAt { get; init; }
}
internal sealed record ReportDeltaPayload

View File

@@ -0,0 +1,322 @@
// -----------------------------------------------------------------------------
// RationaleContracts.cs
// Sprint: SPRINT_20260106_001_001_LB_verdict_rationale_renderer
// Task: VRR-020 - Integrate VerdictRationaleRenderer into Scanner.WebService
// Description: DTOs for verdict rationale endpoint responses.
// -----------------------------------------------------------------------------
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Response for verdict rationale request.
/// </summary>
public sealed record VerdictRationaleResponseDto
{
/// <summary>
/// Finding identifier.
/// </summary>
[JsonPropertyName("finding_id")]
public required string FindingId { get; init; }
/// <summary>
/// Unique rationale ID (content-addressed).
/// </summary>
[JsonPropertyName("rationale_id")]
public required string RationaleId { get; init; }
/// <summary>
/// Schema version.
/// </summary>
[JsonPropertyName("schema_version")]
public string SchemaVersion { get; init; } = "1.0";
/// <summary>
/// Line 1: Evidence summary.
/// </summary>
[JsonPropertyName("evidence")]
public required RationaleEvidenceDto Evidence { get; init; }
/// <summary>
/// Line 2: Policy clause that triggered the decision.
/// </summary>
[JsonPropertyName("policy_clause")]
public required RationalePolicyClauseDto PolicyClause { get; init; }
/// <summary>
/// Line 3: Attestations and proofs.
/// </summary>
[JsonPropertyName("attestations")]
public required RationaleAttestationsDto Attestations { get; init; }
/// <summary>
/// Line 4: Final decision with recommendation.
/// </summary>
[JsonPropertyName("decision")]
public required RationaleDecisionDto Decision { get; init; }
/// <summary>
/// When the rationale was generated.
/// </summary>
[JsonPropertyName("generated_at")]
public required DateTimeOffset GeneratedAt { get; init; }
/// <summary>
/// Input digests for reproducibility verification.
/// </summary>
[JsonPropertyName("input_digests")]
public required RationaleInputDigestsDto InputDigests { get; init; }
}
/// <summary>
/// Line 1: Evidence summary DTO.
/// </summary>
public sealed record RationaleEvidenceDto
{
/// <summary>
/// CVE identifier.
/// </summary>
[JsonPropertyName("cve")]
public required string Cve { get; init; }
/// <summary>
/// Component PURL.
/// </summary>
[JsonPropertyName("component_purl")]
public required string ComponentPurl { get; init; }
/// <summary>
/// Component version.
/// </summary>
[JsonPropertyName("component_version")]
public string? ComponentVersion { get; init; }
/// <summary>
/// Vulnerable function (if reachability analyzed).
/// </summary>
[JsonPropertyName("vulnerable_function")]
public string? VulnerableFunction { get; init; }
/// <summary>
/// Entry point from which vulnerable code is reachable.
/// </summary>
[JsonPropertyName("entry_point")]
public string? EntryPoint { get; init; }
/// <summary>
/// Human-readable formatted text.
/// </summary>
[JsonPropertyName("text")]
public required string Text { get; init; }
}
/// <summary>
/// Line 2: Policy clause DTO.
/// </summary>
public sealed record RationalePolicyClauseDto
{
/// <summary>
/// Policy clause ID.
/// </summary>
[JsonPropertyName("clause_id")]
public required string ClauseId { get; init; }
/// <summary>
/// Rule description.
/// </summary>
[JsonPropertyName("rule_description")]
public required string RuleDescription { get; init; }
/// <summary>
/// Conditions that matched.
/// </summary>
[JsonPropertyName("conditions")]
public required IReadOnlyList<string> Conditions { get; init; }
/// <summary>
/// Human-readable formatted text.
/// </summary>
[JsonPropertyName("text")]
public required string Text { get; init; }
}
/// <summary>
/// Line 3: Attestations DTO.
/// </summary>
public sealed record RationaleAttestationsDto
{
/// <summary>
/// Path witness reference.
/// </summary>
[JsonPropertyName("path_witness")]
public RationaleAttestationRefDto? PathWitness { get; init; }
/// <summary>
/// VEX statement references.
/// </summary>
[JsonPropertyName("vex_statements")]
public IReadOnlyList<RationaleAttestationRefDto>? VexStatements { get; init; }
/// <summary>
/// Provenance attestation reference.
/// </summary>
[JsonPropertyName("provenance")]
public RationaleAttestationRefDto? Provenance { get; init; }
/// <summary>
/// Human-readable formatted text.
/// </summary>
[JsonPropertyName("text")]
public required string Text { get; init; }
}
/// <summary>
/// Attestation reference DTO.
/// </summary>
public sealed record RationaleAttestationRefDto
{
/// <summary>
/// Attestation ID.
/// </summary>
[JsonPropertyName("id")]
public required string Id { get; init; }
/// <summary>
/// Attestation type.
/// </summary>
[JsonPropertyName("type")]
public required string Type { get; init; }
/// <summary>
/// Content digest.
/// </summary>
[JsonPropertyName("digest")]
public string? Digest { get; init; }
/// <summary>
/// Summary description.
/// </summary>
[JsonPropertyName("summary")]
public string? Summary { get; init; }
}
/// <summary>
/// Line 4: Decision DTO.
/// </summary>
public sealed record RationaleDecisionDto
{
/// <summary>
/// Final verdict (Affected, Not Affected, etc.).
/// </summary>
[JsonPropertyName("verdict")]
public required string Verdict { get; init; }
/// <summary>
/// Risk score (0-1).
/// </summary>
[JsonPropertyName("score")]
public double? Score { get; init; }
/// <summary>
/// Recommended action.
/// </summary>
[JsonPropertyName("recommendation")]
public required string Recommendation { get; init; }
/// <summary>
/// Mitigation guidance.
/// </summary>
[JsonPropertyName("mitigation")]
public RationaleMitigationDto? Mitigation { get; init; }
/// <summary>
/// Human-readable formatted text.
/// </summary>
[JsonPropertyName("text")]
public required string Text { get; init; }
}
/// <summary>
/// Mitigation guidance DTO.
/// </summary>
public sealed record RationaleMitigationDto
{
/// <summary>
/// Recommended action.
/// </summary>
[JsonPropertyName("action")]
public required string Action { get; init; }
/// <summary>
/// Additional details.
/// </summary>
[JsonPropertyName("details")]
public string? Details { get; init; }
}
/// <summary>
/// Input digests for reproducibility.
/// </summary>
public sealed record RationaleInputDigestsDto
{
/// <summary>
/// Verdict attestation digest.
/// </summary>
[JsonPropertyName("verdict_digest")]
public required string VerdictDigest { get; init; }
/// <summary>
/// Policy snapshot digest.
/// </summary>
[JsonPropertyName("policy_digest")]
public string? PolicyDigest { get; init; }
/// <summary>
/// Evidence bundle digest.
/// </summary>
[JsonPropertyName("evidence_digest")]
public string? EvidenceDigest { get; init; }
}
/// <summary>
/// Request for rationale in specific format.
/// </summary>
public sealed record RationaleFormatRequestDto
{
/// <summary>
/// Desired format: json, markdown, plaintext.
/// </summary>
[JsonPropertyName("format")]
public string Format { get; init; } = "json";
}
/// <summary>
/// Plain text rationale response.
/// </summary>
public sealed record RationalePlainTextResponseDto
{
/// <summary>
/// Finding identifier.
/// </summary>
[JsonPropertyName("finding_id")]
public required string FindingId { get; init; }
/// <summary>
/// Rationale ID.
/// </summary>
[JsonPropertyName("rationale_id")]
public required string RationaleId { get; init; }
/// <summary>
/// Format of the content.
/// </summary>
[JsonPropertyName("format")]
public required string Format { get; init; }
/// <summary>
/// Rendered content.
/// </summary>
[JsonPropertyName("content")]
public required string Content { get; init; }
}

View File

@@ -0,0 +1,264 @@
// -----------------------------------------------------------------------------
// VexGateContracts.cs
// Sprint: SPRINT_20260106_003_002_SCANNER_vex_gate_service
// Task: T021
// Description: DTOs for VEX gate results API endpoints.
// -----------------------------------------------------------------------------
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Response for GET /scans/{scanId}/gate-results.
/// </summary>
public sealed record VexGateResultsResponse
{
/// <summary>
/// Scan identifier.
/// </summary>
[JsonPropertyName("scanId")]
public required string ScanId { get; init; }
/// <summary>
/// Summary of gate evaluation results.
/// </summary>
[JsonPropertyName("gateSummary")]
public required VexGateSummaryDto GateSummary { get; init; }
/// <summary>
/// Individual gated findings.
/// </summary>
[JsonPropertyName("gatedFindings")]
public required IReadOnlyList<GatedFindingDto> GatedFindings { get; init; }
/// <summary>
/// Policy version used for evaluation.
/// </summary>
[JsonPropertyName("policyVersion")]
public string? PolicyVersion { get; init; }
/// <summary>
/// Whether gate was bypassed for this scan.
/// </summary>
[JsonPropertyName("bypassed")]
public bool Bypassed { get; init; }
}
/// <summary>
/// Summary of VEX gate evaluation.
/// </summary>
public sealed record VexGateSummaryDto
{
/// <summary>
/// Total number of findings evaluated.
/// </summary>
[JsonPropertyName("totalFindings")]
public int TotalFindings { get; init; }
/// <summary>
/// Number of findings that passed (cleared by VEX evidence).
/// </summary>
[JsonPropertyName("passed")]
public int Passed { get; init; }
/// <summary>
/// Number of findings with warnings (partial evidence).
/// </summary>
[JsonPropertyName("warned")]
public int Warned { get; init; }
/// <summary>
/// Number of findings blocked (requires attention).
/// </summary>
[JsonPropertyName("blocked")]
public int Blocked { get; init; }
/// <summary>
/// When the evaluation was performed (UTC ISO-8601).
/// </summary>
[JsonPropertyName("evaluatedAt")]
public DateTimeOffset EvaluatedAt { get; init; }
/// <summary>
/// Percentage of findings that passed.
/// </summary>
[JsonPropertyName("passRate")]
public double PassRate => TotalFindings > 0 ? (double)Passed / TotalFindings : 0;
/// <summary>
/// Percentage of findings that were blocked.
/// </summary>
[JsonPropertyName("blockRate")]
public double BlockRate => TotalFindings > 0 ? (double)Blocked / TotalFindings : 0;
}
/// <summary>
/// A finding with its gate evaluation result.
/// </summary>
public sealed record GatedFindingDto
{
/// <summary>
/// Finding identifier.
/// </summary>
[JsonPropertyName("findingId")]
public required string FindingId { get; init; }
/// <summary>
/// CVE or vulnerability identifier.
/// </summary>
[JsonPropertyName("cve")]
public required string Cve { get; init; }
/// <summary>
/// Package URL of the affected component.
/// </summary>
[JsonPropertyName("purl")]
public string? Purl { get; init; }
/// <summary>
/// Gate decision: Pass, Warn, or Block.
/// </summary>
[JsonPropertyName("decision")]
public required string Decision { get; init; }
/// <summary>
/// Human-readable explanation of the decision.
/// </summary>
[JsonPropertyName("rationale")]
public required string Rationale { get; init; }
/// <summary>
/// ID of the policy rule that matched.
/// </summary>
[JsonPropertyName("policyRuleMatched")]
public required string PolicyRuleMatched { get; init; }
/// <summary>
/// Supporting evidence for the decision.
/// </summary>
[JsonPropertyName("evidence")]
public required GateEvidenceDto Evidence { get; init; }
/// <summary>
/// References to VEX statements that contributed to this decision.
/// </summary>
[JsonPropertyName("contributingStatements")]
public IReadOnlyList<VexStatementRefDto>? ContributingStatements { get; init; }
}
/// <summary>
/// Evidence supporting a gate decision.
/// </summary>
public sealed record GateEvidenceDto
{
/// <summary>
/// VEX status from vendor or authoritative source.
/// </summary>
[JsonPropertyName("vendorStatus")]
public string? VendorStatus { get; init; }
/// <summary>
/// Justification type from VEX statement.
/// </summary>
[JsonPropertyName("justification")]
public string? Justification { get; init; }
/// <summary>
/// Whether the vulnerable code is reachable.
/// </summary>
[JsonPropertyName("isReachable")]
public bool IsReachable { get; init; }
/// <summary>
/// Whether compensating controls mitigate the vulnerability.
/// </summary>
[JsonPropertyName("hasCompensatingControl")]
public bool HasCompensatingControl { get; init; }
/// <summary>
/// Confidence score in the gate decision (0.0 to 1.0).
/// </summary>
[JsonPropertyName("confidenceScore")]
public double ConfidenceScore { get; init; }
/// <summary>
/// Severity level from the advisory.
/// </summary>
[JsonPropertyName("severityLevel")]
public string? SeverityLevel { get; init; }
/// <summary>
/// Whether the vulnerability is exploitable.
/// </summary>
[JsonPropertyName("isExploitable")]
public bool IsExploitable { get; init; }
/// <summary>
/// Backport hints detected.
/// </summary>
[JsonPropertyName("backportHints")]
public IReadOnlyList<string>? BackportHints { get; init; }
}
/// <summary>
/// Reference to a VEX statement.
/// </summary>
public sealed record VexStatementRefDto
{
/// <summary>
/// Statement identifier.
/// </summary>
[JsonPropertyName("statementId")]
public required string StatementId { get; init; }
/// <summary>
/// Issuer identifier.
/// </summary>
[JsonPropertyName("issuerId")]
public required string IssuerId { get; init; }
/// <summary>
/// VEX status declared in the statement.
/// </summary>
[JsonPropertyName("status")]
public required string Status { get; init; }
/// <summary>
/// When the statement was issued (UTC ISO-8601).
/// </summary>
[JsonPropertyName("timestamp")]
public DateTimeOffset Timestamp { get; init; }
/// <summary>
/// Trust weight of this statement (0.0 to 1.0).
/// </summary>
[JsonPropertyName("trustWeight")]
public double TrustWeight { get; init; }
}
/// <summary>
/// Query parameters for filtering gate results.
/// </summary>
public sealed record VexGateResultsQuery
{
/// <summary>
/// Filter by gate decision (Pass, Warn, Block).
/// </summary>
public string? Decision { get; init; }
/// <summary>
/// Filter by minimum confidence score.
/// </summary>
public double? MinConfidence { get; init; }
/// <summary>
/// Maximum number of results to return.
/// </summary>
public int? Limit { get; init; }
/// <summary>
/// Offset for pagination.
/// </summary>
public int? Offset { get; init; }
}