663 lines
20 KiB
C#
663 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Scanner.WebService.Contracts;
|
|
|
|
internal static class OrchestratorEventKinds
|
|
{
|
|
public const string ScannerReportReady = "scanner.event.report.ready";
|
|
public const string ScannerScanCompleted = "scanner.event.scan.completed";
|
|
public const string ScannerScanStarted = "scanner.event.scan.started";
|
|
public const string ScannerScanFailed = "scanner.event.scan.failed";
|
|
public const string ScannerSbomGenerated = "scanner.event.sbom.generated";
|
|
public const string ScannerVulnerabilityDetected = "scanner.event.vulnerability.detected";
|
|
}
|
|
|
|
internal sealed record OrchestratorEvent
|
|
{
|
|
[JsonPropertyName("eventId")]
|
|
[JsonPropertyOrder(0)]
|
|
public Guid EventId { get; init; }
|
|
|
|
[JsonPropertyName("kind")]
|
|
[JsonPropertyOrder(1)]
|
|
public string Kind { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("version")]
|
|
[JsonPropertyOrder(2)]
|
|
public int Version { get; init; } = 1;
|
|
|
|
[JsonPropertyName("tenant")]
|
|
[JsonPropertyOrder(3)]
|
|
public string Tenant { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("occurredAt")]
|
|
[JsonPropertyOrder(4)]
|
|
public DateTimeOffset OccurredAt { get; init; }
|
|
|
|
[JsonPropertyName("recordedAt")]
|
|
[JsonPropertyOrder(5)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public DateTimeOffset? RecordedAt { get; init; }
|
|
|
|
[JsonPropertyName("source")]
|
|
[JsonPropertyOrder(6)]
|
|
public string Source { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("idempotencyKey")]
|
|
[JsonPropertyOrder(7)]
|
|
public string IdempotencyKey { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("correlationId")]
|
|
[JsonPropertyOrder(8)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? CorrelationId { get; init; }
|
|
|
|
[JsonPropertyName("traceId")]
|
|
[JsonPropertyOrder(9)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? TraceId { get; init; }
|
|
|
|
[JsonPropertyName("spanId")]
|
|
[JsonPropertyOrder(10)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? SpanId { get; init; }
|
|
|
|
[JsonPropertyName("scope")]
|
|
[JsonPropertyOrder(11)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public OrchestratorEventScope? Scope { get; init; }
|
|
|
|
[JsonPropertyName("payload")]
|
|
[JsonPropertyOrder(12)]
|
|
public OrchestratorEventPayload Payload { get; init; } = default!;
|
|
|
|
[JsonPropertyName("attributes")]
|
|
[JsonPropertyOrder(13)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public ImmutableSortedDictionary<string, string>? Attributes { get; init; }
|
|
|
|
[JsonPropertyName("notifier")]
|
|
[JsonPropertyOrder(14)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public NotifierIngestionMetadata? Notifier { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metadata for Notifier service ingestion per orchestrator-envelope.schema.json.
|
|
/// </summary>
|
|
internal sealed record NotifierIngestionMetadata
|
|
{
|
|
[JsonPropertyName("severityThresholdMet")]
|
|
[JsonPropertyOrder(0)]
|
|
public bool SeverityThresholdMet { get; init; }
|
|
|
|
[JsonPropertyName("notificationChannels")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public IReadOnlyList<string>? NotificationChannels { get; init; }
|
|
|
|
[JsonPropertyName("digestEligible")]
|
|
[JsonPropertyOrder(2)]
|
|
public bool DigestEligible { get; init; } = true;
|
|
|
|
[JsonPropertyName("immediateDispatch")]
|
|
[JsonPropertyOrder(3)]
|
|
public bool ImmediateDispatch { get; init; }
|
|
|
|
[JsonPropertyName("priority")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Priority { get; init; }
|
|
}
|
|
|
|
internal sealed record OrchestratorEventScope
|
|
{
|
|
[JsonPropertyName("namespace")]
|
|
[JsonPropertyOrder(0)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Namespace { get; init; }
|
|
|
|
[JsonPropertyName("repo")]
|
|
[JsonPropertyOrder(1)]
|
|
public string Repo { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("digest")]
|
|
[JsonPropertyOrder(2)]
|
|
public string Digest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("component")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Component { get; init; }
|
|
|
|
[JsonPropertyName("image")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Image { get; init; }
|
|
}
|
|
|
|
internal abstract record OrchestratorEventPayload;
|
|
|
|
internal sealed record ReportReadyEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("reportId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ReportId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? ScanId { get; init; }
|
|
|
|
[JsonPropertyName("imageDigest")]
|
|
[JsonPropertyOrder(2)]
|
|
public string ImageDigest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("generatedAt")]
|
|
[JsonPropertyOrder(3)]
|
|
public DateTimeOffset GeneratedAt { get; init; }
|
|
|
|
[JsonPropertyName("verdict")]
|
|
[JsonPropertyOrder(4)]
|
|
public string Verdict { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("summary")]
|
|
[JsonPropertyOrder(5)]
|
|
public ReportSummaryDto Summary { get; init; } = new();
|
|
|
|
[JsonPropertyName("delta")]
|
|
[JsonPropertyOrder(6)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public ReportDeltaPayload? Delta { get; init; }
|
|
|
|
[JsonPropertyName("quietedFindingCount")]
|
|
[JsonPropertyOrder(7)]
|
|
public int QuietedFindingCount { get; init; }
|
|
|
|
[JsonPropertyName("policy")]
|
|
[JsonPropertyOrder(8)]
|
|
public ReportPolicyDto Policy { get; init; } = new();
|
|
|
|
[JsonPropertyName("links")]
|
|
[JsonPropertyOrder(9)]
|
|
public ReportLinksPayload Links { get; init; } = new();
|
|
|
|
[JsonPropertyName("dsse")]
|
|
[JsonPropertyOrder(10)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public DsseEnvelopeDto? Dsse { get; init; }
|
|
|
|
[JsonPropertyName("report")]
|
|
[JsonPropertyOrder(11)]
|
|
public ReportDocumentDto Report { get; init; } = new();
|
|
}
|
|
|
|
internal sealed record ScanCompletedEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("reportId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ReportId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? ScanId { get; init; }
|
|
|
|
[JsonPropertyName("imageDigest")]
|
|
[JsonPropertyOrder(2)]
|
|
public string ImageDigest { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("verdict")]
|
|
[JsonPropertyOrder(3)]
|
|
public string Verdict { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("summary")]
|
|
[JsonPropertyOrder(4)]
|
|
public ReportSummaryDto Summary { get; init; } = new();
|
|
|
|
[JsonPropertyName("delta")]
|
|
[JsonPropertyOrder(5)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public ReportDeltaPayload? Delta { get; init; }
|
|
|
|
[JsonPropertyName("policy")]
|
|
[JsonPropertyOrder(6)]
|
|
public ReportPolicyDto Policy { get; init; } = new();
|
|
|
|
[JsonPropertyName("findings")]
|
|
[JsonPropertyOrder(7)]
|
|
public IReadOnlyList<FindingSummaryPayload> Findings { get; init; } = Array.Empty<FindingSummaryPayload>();
|
|
|
|
[JsonPropertyName("links")]
|
|
[JsonPropertyOrder(8)]
|
|
public ReportLinksPayload Links { get; init; } = new();
|
|
|
|
[JsonPropertyName("dsse")]
|
|
[JsonPropertyOrder(9)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public DsseEnvelopeDto? Dsse { get; init; }
|
|
|
|
[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
|
|
{
|
|
[JsonPropertyName("newCritical")]
|
|
[JsonPropertyOrder(0)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public int? NewCritical { get; init; }
|
|
|
|
[JsonPropertyName("newHigh")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public int? NewHigh { get; init; }
|
|
|
|
[JsonPropertyName("kev")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public IReadOnlyList<string>? Kev { get; init; }
|
|
}
|
|
|
|
internal sealed record ReportLinksPayload
|
|
{
|
|
[JsonPropertyName("report")]
|
|
[JsonPropertyOrder(0)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public LinkTarget? Report { get; init; }
|
|
|
|
[JsonPropertyName("policy")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public LinkTarget? Policy { get; init; }
|
|
|
|
[JsonPropertyName("attestation")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public LinkTarget? Attestation { get; init; }
|
|
}
|
|
|
|
internal sealed record LinkTarget(
|
|
[property: JsonPropertyName("ui"), JsonPropertyOrder(0), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Ui,
|
|
[property: JsonPropertyName("api"), JsonPropertyOrder(1), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Api)
|
|
{
|
|
public static LinkTarget? Create(string? ui, string? api)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ui) && string.IsNullOrWhiteSpace(api))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LinkTarget(
|
|
string.IsNullOrWhiteSpace(ui) ? null : ui,
|
|
string.IsNullOrWhiteSpace(api) ? null : api);
|
|
}
|
|
}
|
|
|
|
internal sealed record FindingSummaryPayload
|
|
{
|
|
[JsonPropertyName("id")]
|
|
[JsonPropertyOrder(0)]
|
|
public string Id { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("severity")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Severity { get; init; }
|
|
|
|
[JsonPropertyName("cve")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Cve { get; init; }
|
|
|
|
[JsonPropertyName("purl")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Purl { get; init; }
|
|
|
|
[JsonPropertyName("reachability")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Reachability { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for scanner.event.scan.started events.
|
|
/// </summary>
|
|
internal sealed record ScanStartedEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ScanId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("jobId")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? JobId { get; init; }
|
|
|
|
[JsonPropertyName("target")]
|
|
[JsonPropertyOrder(2)]
|
|
public ScanTargetPayload Target { get; init; } = new();
|
|
|
|
[JsonPropertyName("startedAt")]
|
|
[JsonPropertyOrder(3)]
|
|
public DateTimeOffset StartedAt { get; init; }
|
|
|
|
[JsonPropertyName("status")]
|
|
[JsonPropertyOrder(4)]
|
|
public string Status { get; init; } = "started";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for scanner.event.scan.failed events.
|
|
/// </summary>
|
|
internal sealed record ScanFailedEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ScanId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("jobId")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? JobId { get; init; }
|
|
|
|
[JsonPropertyName("target")]
|
|
[JsonPropertyOrder(2)]
|
|
public ScanTargetPayload Target { get; init; } = new();
|
|
|
|
[JsonPropertyName("startedAt")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public DateTimeOffset? StartedAt { get; init; }
|
|
|
|
[JsonPropertyName("failedAt")]
|
|
[JsonPropertyOrder(4)]
|
|
public DateTimeOffset FailedAt { get; init; }
|
|
|
|
[JsonPropertyName("durationMs")]
|
|
[JsonPropertyOrder(5)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public long? DurationMs { get; init; }
|
|
|
|
[JsonPropertyName("status")]
|
|
[JsonPropertyOrder(6)]
|
|
public string Status { get; init; } = "failed";
|
|
|
|
[JsonPropertyName("error")]
|
|
[JsonPropertyOrder(7)]
|
|
public ScanErrorPayload Error { get; init; } = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for scanner.event.sbom.generated events.
|
|
/// </summary>
|
|
internal sealed record SbomGeneratedEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ScanId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("sbomId")]
|
|
[JsonPropertyOrder(1)]
|
|
public string SbomId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("target")]
|
|
[JsonPropertyOrder(2)]
|
|
public ScanTargetPayload Target { get; init; } = new();
|
|
|
|
[JsonPropertyName("generatedAt")]
|
|
[JsonPropertyOrder(3)]
|
|
public DateTimeOffset GeneratedAt { get; init; }
|
|
|
|
[JsonPropertyName("format")]
|
|
[JsonPropertyOrder(4)]
|
|
public string Format { get; init; } = "cyclonedx";
|
|
|
|
[JsonPropertyName("specVersion")]
|
|
[JsonPropertyOrder(5)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? SpecVersion { get; init; }
|
|
|
|
[JsonPropertyName("componentCount")]
|
|
[JsonPropertyOrder(6)]
|
|
public int ComponentCount { get; init; }
|
|
|
|
[JsonPropertyName("sbomRef")]
|
|
[JsonPropertyOrder(7)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? SbomRef { get; init; }
|
|
|
|
[JsonPropertyName("digest")]
|
|
[JsonPropertyOrder(8)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Digest { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for scanner.event.vulnerability.detected events.
|
|
/// </summary>
|
|
internal sealed record VulnerabilityDetectedEventPayload : OrchestratorEventPayload
|
|
{
|
|
[JsonPropertyName("scanId")]
|
|
[JsonPropertyOrder(0)]
|
|
public string ScanId { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("vulnerability")]
|
|
[JsonPropertyOrder(1)]
|
|
public VulnerabilityInfoPayload Vulnerability { get; init; } = new();
|
|
|
|
[JsonPropertyName("affectedComponent")]
|
|
[JsonPropertyOrder(2)]
|
|
public ComponentInfoPayload AffectedComponent { get; init; } = new();
|
|
|
|
[JsonPropertyName("reachability")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Reachability { get; init; }
|
|
|
|
[JsonPropertyName("detectedAt")]
|
|
[JsonPropertyOrder(4)]
|
|
public DateTimeOffset DetectedAt { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target being scanned.
|
|
/// </summary>
|
|
internal sealed record ScanTargetPayload
|
|
{
|
|
[JsonPropertyName("type")]
|
|
[JsonPropertyOrder(0)]
|
|
public string Type { get; init; } = "container_image";
|
|
|
|
[JsonPropertyName("identifier")]
|
|
[JsonPropertyOrder(1)]
|
|
public string Identifier { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("digest")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Digest { get; init; }
|
|
|
|
[JsonPropertyName("tag")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Tag { get; init; }
|
|
|
|
[JsonPropertyName("platform")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Platform { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error information for failed scans.
|
|
/// </summary>
|
|
internal sealed record ScanErrorPayload
|
|
{
|
|
[JsonPropertyName("code")]
|
|
[JsonPropertyOrder(0)]
|
|
public string Code { get; init; } = "SCAN_FAILED";
|
|
|
|
[JsonPropertyName("message")]
|
|
[JsonPropertyOrder(1)]
|
|
public string Message { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("details")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public ImmutableDictionary<string, string>? Details { get; init; }
|
|
|
|
[JsonPropertyName("recoverable")]
|
|
[JsonPropertyOrder(3)]
|
|
public bool Recoverable { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vulnerability information.
|
|
/// </summary>
|
|
internal sealed record VulnerabilityInfoPayload
|
|
{
|
|
[JsonPropertyName("id")]
|
|
[JsonPropertyOrder(0)]
|
|
public string Id { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("severity")]
|
|
[JsonPropertyOrder(1)]
|
|
public string Severity { get; init; } = "unknown";
|
|
|
|
[JsonPropertyName("cvssScore")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public double? CvssScore { get; init; }
|
|
|
|
[JsonPropertyName("cvssVector")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? CvssVector { get; init; }
|
|
|
|
[JsonPropertyName("title")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Title { get; init; }
|
|
|
|
[JsonPropertyName("fixAvailable")]
|
|
[JsonPropertyOrder(5)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public bool? FixAvailable { get; init; }
|
|
|
|
[JsonPropertyName("fixedVersion")]
|
|
[JsonPropertyOrder(6)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? FixedVersion { get; init; }
|
|
|
|
[JsonPropertyName("kevListed")]
|
|
[JsonPropertyOrder(7)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public bool? KevListed { get; init; }
|
|
|
|
[JsonPropertyName("epssScore")]
|
|
[JsonPropertyOrder(8)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public double? EpssScore { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Component information.
|
|
/// </summary>
|
|
internal sealed record ComponentInfoPayload
|
|
{
|
|
[JsonPropertyName("purl")]
|
|
[JsonPropertyOrder(0)]
|
|
public string Purl { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("name")]
|
|
[JsonPropertyOrder(1)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Name { get; init; }
|
|
|
|
[JsonPropertyName("version")]
|
|
[JsonPropertyOrder(2)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Version { get; init; }
|
|
|
|
[JsonPropertyName("ecosystem")]
|
|
[JsonPropertyOrder(3)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Ecosystem { get; init; }
|
|
|
|
[JsonPropertyName("location")]
|
|
[JsonPropertyOrder(4)]
|
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
public string? Location { get; init; }
|
|
}
|