sprints work

This commit is contained in:
StellaOps Bot
2025-12-24 21:46:08 +02:00
parent 43e2af88f6
commit b9f71fc7e9
161 changed files with 29566 additions and 527 deletions

View File

@@ -0,0 +1,264 @@
// -----------------------------------------------------------------------------
// GatingContracts.cs
// Sprint: SPRINT_9200_0001_0001_SCANNER_gated_triage_contracts
// Description: DTOs for gating explainability in triage.
// Provides visibility into why findings are hidden by default.
// -----------------------------------------------------------------------------
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Reasons why a finding is hidden by default in quiet-by-design triage.
/// </summary>
public enum GatingReason
{
/// <summary>Not gated - visible in default view.</summary>
None = 0,
/// <summary>Finding is not reachable from any entrypoint.</summary>
Unreachable = 1,
/// <summary>Policy rule dismissed this finding (waived, tolerated).</summary>
PolicyDismissed = 2,
/// <summary>Patched via distro backport; version comparison confirms fixed.</summary>
Backported = 3,
/// <summary>VEX statement declares not_affected with sufficient trust.</summary>
VexNotAffected = 4,
/// <summary>Superseded by newer advisory or CVE.</summary>
Superseded = 5,
/// <summary>Muted by user decision (explicit acknowledgement).</summary>
UserMuted = 6
}
/// <summary>
/// Extended finding status with gating explainability.
/// </summary>
public sealed record FindingGatingStatusDto
{
/// <summary>
/// Why this finding is gated (hidden by default).
/// </summary>
public GatingReason GatingReason { get; init; } = GatingReason.None;
/// <summary>
/// True if this finding is hidden in the default view.
/// </summary>
public bool IsHiddenByDefault { get; init; }
/// <summary>
/// Link to reachability subgraph for one-click drill-down.
/// </summary>
public string? SubgraphId { get; init; }
/// <summary>
/// Link to delta comparison for "what changed" analysis.
/// </summary>
public string? DeltasId { get; init; }
/// <summary>
/// Human-readable explanation of why this finding is gated.
/// </summary>
public string? GatingExplanation { get; init; }
/// <summary>
/// Criteria that would make this finding visible (un-gate it).
/// </summary>
public IReadOnlyList<string>? WouldShowIf { get; init; }
}
/// <summary>
/// Extended VEX status with trust scoring.
/// </summary>
public sealed record TriageVexTrustStatusDto
{
/// <summary>
/// Base VEX status.
/// </summary>
public required TriageVexStatusDto VexStatus { get; init; }
/// <summary>
/// Composite trust score (0.0-1.0).
/// </summary>
public double? TrustScore { get; init; }
/// <summary>
/// Policy-defined minimum trust threshold.
/// </summary>
public double? PolicyTrustThreshold { get; init; }
/// <summary>
/// True if TrustScore >= PolicyTrustThreshold.
/// </summary>
public bool? MeetsPolicyThreshold { get; init; }
/// <summary>
/// Breakdown of trust score components.
/// </summary>
public VexTrustBreakdownDto? TrustBreakdown { get; init; }
}
/// <summary>
/// Breakdown of VEX trust score components.
/// </summary>
public sealed record VexTrustBreakdownDto
{
/// <summary>
/// Trust based on issuer authority.
/// </summary>
public double IssuerTrust { get; init; }
/// <summary>
/// Trust based on recency of statement.
/// </summary>
public double RecencyTrust { get; init; }
/// <summary>
/// Trust based on justification quality.
/// </summary>
public double JustificationTrust { get; init; }
/// <summary>
/// Trust based on supporting evidence.
/// </summary>
public double EvidenceTrust { get; init; }
/// <summary>
/// Consensus score across multiple VEX sources.
/// </summary>
public double? ConsensusScore { get; init; }
}
/// <summary>
/// Summary counts of hidden findings by gating reason.
/// </summary>
public sealed record GatedBucketsSummaryDto
{
/// <summary>
/// Count of findings hidden due to unreachability.
/// </summary>
public int UnreachableCount { get; init; }
/// <summary>
/// Count of findings hidden due to policy dismissal.
/// </summary>
public int PolicyDismissedCount { get; init; }
/// <summary>
/// Count of findings hidden due to backport fix.
/// </summary>
public int BackportedCount { get; init; }
/// <summary>
/// Count of findings hidden due to VEX not_affected.
/// </summary>
public int VexNotAffectedCount { get; init; }
/// <summary>
/// Count of findings hidden due to superseded CVE.
/// </summary>
public int SupersededCount { get; init; }
/// <summary>
/// Count of findings hidden due to user muting.
/// </summary>
public int UserMutedCount { get; init; }
/// <summary>
/// Total count of all hidden findings.
/// </summary>
public int TotalHiddenCount => UnreachableCount + PolicyDismissedCount +
BackportedCount + VexNotAffectedCount + SupersededCount + UserMutedCount;
/// <summary>
/// Creates an empty summary with all zero counts.
/// </summary>
public static GatedBucketsSummaryDto Empty => new();
}
/// <summary>
/// Extended bulk triage response with gated bucket counts.
/// </summary>
public sealed record BulkTriageQueryWithGatingResponseDto
{
/// <summary>
/// The findings matching the query.
/// </summary>
public required IReadOnlyList<FindingTriageStatusWithGatingDto> Findings { get; init; }
/// <summary>
/// Total count matching the query (visible + hidden).
/// </summary>
public int TotalCount { get; init; }
/// <summary>
/// Count of visible findings (not gated).
/// </summary>
public int VisibleCount { get; init; }
/// <summary>
/// Next cursor for pagination.
/// </summary>
public string? NextCursor { get; init; }
/// <summary>
/// Summary statistics.
/// </summary>
public TriageSummaryDto? Summary { get; init; }
/// <summary>
/// Gated bucket counts for chip display.
/// </summary>
public GatedBucketsSummaryDto? GatedBuckets { get; init; }
}
/// <summary>
/// Extended finding triage status with gating information.
/// </summary>
public sealed record FindingTriageStatusWithGatingDto
{
/// <summary>
/// Base finding triage status.
/// </summary>
public required FindingTriageStatusDto BaseStatus { get; init; }
/// <summary>
/// Gating status information.
/// </summary>
public FindingGatingStatusDto? Gating { get; init; }
/// <summary>
/// Extended VEX status with trust scoring.
/// </summary>
public TriageVexTrustStatusDto? VexTrust { get; init; }
}
/// <summary>
/// Request to query findings with gating information.
/// </summary>
public sealed record BulkTriageQueryWithGatingRequestDto
{
/// <summary>
/// Base query parameters.
/// </summary>
public required BulkTriageQueryRequestDto Query { get; init; }
/// <summary>
/// Whether to include hidden findings in results.
/// Default: false (only visible findings).
/// </summary>
public bool IncludeHidden { get; init; }
/// <summary>
/// Filter to specific gating reasons.
/// </summary>
public IReadOnlyList<GatingReason>? GatingReasonFilter { get; init; }
/// <summary>
/// Minimum VEX trust score filter.
/// </summary>
public double? MinVexTrustScore { get; init; }
}

View File

@@ -0,0 +1,212 @@
// -----------------------------------------------------------------------------
// ReplayCommandContracts.cs
// Sprint: SPRINT_9200_0001_0003_SCANNER_replay_command_generator
// Description: DTOs for generating copy-ready CLI commands that replay
// verdicts deterministically.
// -----------------------------------------------------------------------------
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Response containing replay commands for reproducing a verdict.
/// </summary>
public sealed record ReplayCommandResponseDto
{
/// <summary>Finding ID this replay is for.</summary>
public required string FindingId { get; init; }
/// <summary>Scan ID this replay is for.</summary>
public required string ScanId { get; init; }
// === Full Command ===
/// <summary>Full replay command with all inline parameters.</summary>
public required ReplayCommandDto FullCommand { get; init; }
// === Short Command ===
/// <summary>Short command using snapshot ID reference.</summary>
public ReplayCommandDto? ShortCommand { get; init; }
// === Offline Command ===
/// <summary>Command for offline/air-gapped replay.</summary>
public ReplayCommandDto? OfflineCommand { get; init; }
// === Snapshot Information ===
/// <summary>Knowledge snapshot used for this verdict.</summary>
public SnapshotInfoDto? Snapshot { get; init; }
// === Bundle Information ===
/// <summary>Evidence bundle download information.</summary>
public EvidenceBundleInfoDto? Bundle { get; init; }
// === Metadata ===
/// <summary>When this command was generated.</summary>
public required DateTimeOffset GeneratedAt { get; init; }
/// <summary>Expected verdict hash - verification target.</summary>
public required string ExpectedVerdictHash { get; init; }
}
/// <summary>
/// A single replay command variant.
/// </summary>
public sealed record ReplayCommandDto
{
/// <summary>Command type (full, short, offline).</summary>
public required string Type { get; init; }
/// <summary>Complete command string ready to copy.</summary>
public required string Command { get; init; }
/// <summary>Shell type (bash, powershell, cmd).</summary>
public string Shell { get; init; } = "bash";
/// <summary>Command broken into structured parts.</summary>
public ReplayCommandPartsDto? Parts { get; init; }
/// <summary>Whether this command requires network access.</summary>
public bool RequiresNetwork { get; init; }
/// <summary>Prerequisites for running this command.</summary>
public IReadOnlyList<string>? Prerequisites { get; init; }
}
/// <summary>
/// Structured parts of a replay command.
/// </summary>
public sealed record ReplayCommandPartsDto
{
/// <summary>CLI binary name.</summary>
public required string Binary { get; init; }
/// <summary>Subcommand (e.g., "scan", "replay").</summary>
public required string Subcommand { get; init; }
/// <summary>Target (image reference, SBOM path, etc.).</summary>
public required string Target { get; init; }
/// <summary>Named arguments as key-value pairs.</summary>
public IReadOnlyDictionary<string, string>? Arguments { get; init; }
/// <summary>Boolean flags.</summary>
public IReadOnlyList<string>? Flags { get; init; }
}
/// <summary>
/// Knowledge snapshot information.
/// </summary>
public sealed record SnapshotInfoDto
{
/// <summary>Snapshot ID.</summary>
public required string Id { get; init; }
/// <summary>Snapshot creation timestamp.</summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>Feed versions included.</summary>
public IReadOnlyDictionary<string, string>? FeedVersions { get; init; }
/// <summary>How to obtain this snapshot.</summary>
public string? DownloadUri { get; init; }
/// <summary>Snapshot content hash.</summary>
public string? ContentHash { get; init; }
}
/// <summary>
/// Evidence bundle download information.
/// </summary>
public sealed record EvidenceBundleInfoDto
{
/// <summary>Bundle ID.</summary>
public required string Id { get; init; }
/// <summary>Download URL.</summary>
public required string DownloadUri { get; init; }
/// <summary>Bundle size in bytes.</summary>
public long? SizeBytes { get; init; }
/// <summary>Bundle content hash.</summary>
public required string ContentHash { get; init; }
/// <summary>Bundle format (tar.gz, zip).</summary>
public string Format { get; init; } = "tar.gz";
/// <summary>When this bundle expires.</summary>
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>Contents manifest.</summary>
public IReadOnlyList<string>? Contents { get; init; }
}
/// <summary>
/// Request to generate replay commands for a finding.
/// </summary>
public sealed record GenerateReplayCommandRequestDto
{
/// <summary>Finding ID.</summary>
public required string FindingId { get; init; }
/// <summary>Target shells to generate for.</summary>
public IReadOnlyList<string>? Shells { get; init; }
/// <summary>Include offline variant.</summary>
public bool IncludeOffline { get; init; }
/// <summary>Generate evidence bundle.</summary>
public bool GenerateBundle { get; init; }
}
/// <summary>
/// Request to generate replay commands for a scan.
/// </summary>
public sealed record GenerateScanReplayCommandRequestDto
{
/// <summary>Scan ID.</summary>
public required string ScanId { get; init; }
/// <summary>Target shells to generate for.</summary>
public IReadOnlyList<string>? Shells { get; init; }
/// <summary>Include offline variant.</summary>
public bool IncludeOffline { get; init; }
/// <summary>Generate evidence bundle.</summary>
public bool GenerateBundle { get; init; }
}
/// <summary>
/// Response for scan-level replay command.
/// </summary>
public sealed record ScanReplayCommandResponseDto
{
/// <summary>Scan ID.</summary>
public required string ScanId { get; init; }
/// <summary>Full replay command.</summary>
public required ReplayCommandDto FullCommand { get; init; }
/// <summary>Short command using snapshot.</summary>
public ReplayCommandDto? ShortCommand { get; init; }
/// <summary>Offline replay command.</summary>
public ReplayCommandDto? OfflineCommand { get; init; }
/// <summary>Snapshot information.</summary>
public SnapshotInfoDto? Snapshot { get; init; }
/// <summary>Bundle information.</summary>
public EvidenceBundleInfoDto? Bundle { get; init; }
/// <summary>Generation timestamp.</summary>
public required DateTimeOffset GeneratedAt { get; init; }
/// <summary>Expected final digest.</summary>
public required string ExpectedFinalDigest { get; init; }
}

View File

@@ -0,0 +1,390 @@
// -----------------------------------------------------------------------------
// UnifiedEvidenceContracts.cs
// Sprint: SPRINT_9200_0001_0002_SCANNER_unified_evidence_endpoint
// Description: DTOs for unified evidence endpoint that returns all evidence
// tabs for a finding in one API call.
// -----------------------------------------------------------------------------
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Complete evidence package for a finding - all tabs in one response.
/// </summary>
public sealed record UnifiedEvidenceResponseDto
{
/// <summary>Finding this evidence applies to.</summary>
public required string FindingId { get; init; }
/// <summary>CVE identifier.</summary>
public required string CveId { get; init; }
/// <summary>Affected component PURL.</summary>
public required string ComponentPurl { get; init; }
// === Evidence Tabs ===
/// <summary>SBOM evidence - component metadata and linkage.</summary>
public SbomEvidenceDto? Sbom { get; init; }
/// <summary>Reachability evidence - call paths to vulnerable code.</summary>
public ReachabilityEvidenceDto? Reachability { get; init; }
/// <summary>VEX claims from all sources with trust scores.</summary>
public IReadOnlyList<VexClaimDto>? VexClaims { get; init; }
/// <summary>Attestations (in-toto/DSSE) for this artifact.</summary>
public IReadOnlyList<AttestationSummaryDto>? Attestations { get; init; }
/// <summary>Delta comparison since last scan.</summary>
public DeltaEvidenceDto? Deltas { get; init; }
/// <summary>Policy evaluation evidence.</summary>
public PolicyEvidenceDto? Policy { get; init; }
// === Manifest Hashes ===
/// <summary>Content-addressed hashes for determinism verification.</summary>
public required ManifestHashesDto Manifests { get; init; }
// === Verification Status ===
/// <summary>Overall verification status of evidence chain.</summary>
public required VerificationStatusDto Verification { get; init; }
// === Replay Command ===
/// <summary>Copy-ready CLI command to replay this verdict.</summary>
public string? ReplayCommand { get; init; }
/// <summary>Shortened replay command using snapshot ID.</summary>
public string? ShortReplayCommand { get; init; }
/// <summary>URL to download complete evidence bundle.</summary>
public string? EvidenceBundleUrl { get; init; }
// === Metadata ===
/// <summary>When this evidence was assembled.</summary>
public required DateTimeOffset GeneratedAt { get; init; }
/// <summary>Cache key for this response (content-addressed).</summary>
public string? CacheKey { get; init; }
}
/// <summary>
/// SBOM evidence for evidence panel.
/// </summary>
public sealed record SbomEvidenceDto
{
/// <summary>SBOM format (spdx, cyclonedx).</summary>
public required string Format { get; init; }
/// <summary>SBOM version.</summary>
public required string Version { get; init; }
/// <summary>Link to full SBOM document.</summary>
public required string DocumentUri { get; init; }
/// <summary>SBOM content digest.</summary>
public required string Digest { get; init; }
/// <summary>Component entry from SBOM.</summary>
public SbomComponentDto? Component { get; init; }
/// <summary>Dependencies of this component.</summary>
public IReadOnlyList<string>? Dependencies { get; init; }
/// <summary>Dependents (things that depend on this component).</summary>
public IReadOnlyList<string>? Dependents { get; init; }
}
/// <summary>
/// Component information from SBOM.
/// </summary>
public sealed record SbomComponentDto
{
/// <summary>Package URL.</summary>
public required string Purl { get; init; }
/// <summary>Component name.</summary>
public required string Name { get; init; }
/// <summary>Component version.</summary>
public required string Version { get; init; }
/// <summary>Ecosystem (npm, maven, pypi, etc.).</summary>
public string? Ecosystem { get; init; }
/// <summary>License(s).</summary>
public IReadOnlyList<string>? Licenses { get; init; }
/// <summary>CPE identifiers.</summary>
public IReadOnlyList<string>? Cpes { get; init; }
}
/// <summary>
/// Reachability evidence for evidence panel.
/// </summary>
public sealed record ReachabilityEvidenceDto
{
/// <summary>Subgraph ID for detailed view.</summary>
public required string SubgraphId { get; init; }
/// <summary>Reachability status.</summary>
public required string Status { get; init; }
/// <summary>Confidence level (0-1).</summary>
public double Confidence { get; init; }
/// <summary>Analysis method (static, binary, runtime).</summary>
public required string Method { get; init; }
/// <summary>Entry points reaching vulnerable code.</summary>
public IReadOnlyList<EntryPointDto>? EntryPoints { get; init; }
/// <summary>Call chain summary.</summary>
public CallChainSummaryDto? CallChain { get; init; }
/// <summary>Link to full reachability graph.</summary>
public string? GraphUri { get; init; }
}
/// <summary>
/// Entry point information.
/// </summary>
public sealed record EntryPointDto
{
/// <summary>Entry point identifier.</summary>
public required string Id { get; init; }
/// <summary>Entry point type (http, grpc, function, etc.).</summary>
public required string Type { get; init; }
/// <summary>Display name.</summary>
public required string Name { get; init; }
/// <summary>File location if known.</summary>
public string? Location { get; init; }
/// <summary>Distance (hops) to vulnerable code.</summary>
public int? Distance { get; init; }
}
/// <summary>
/// Summary of call chain to vulnerable code.
/// </summary>
public sealed record CallChainSummaryDto
{
/// <summary>Total path length.</summary>
public int PathLength { get; init; }
/// <summary>Number of distinct paths.</summary>
public int PathCount { get; init; }
/// <summary>Key symbols in the chain.</summary>
public IReadOnlyList<string>? KeySymbols { get; init; }
/// <summary>Link to full call graph.</summary>
public string? CallGraphUri { get; init; }
}
/// <summary>
/// VEX claim with trust scoring.
/// </summary>
public sealed record VexClaimDto
{
/// <summary>VEX statement ID.</summary>
public required string StatementId { get; init; }
/// <summary>Source of the VEX statement.</summary>
public required string Source { get; init; }
/// <summary>Status (affected, not_affected, etc.).</summary>
public required string Status { get; init; }
/// <summary>Justification category.</summary>
public string? Justification { get; init; }
/// <summary>Impact statement.</summary>
public string? ImpactStatement { get; init; }
/// <summary>When issued.</summary>
public DateTimeOffset IssuedAt { get; init; }
/// <summary>Trust score (0-1).</summary>
public double TrustScore { get; init; }
/// <summary>Whether this meets policy threshold.</summary>
public bool MeetsPolicyThreshold { get; init; }
/// <summary>Link to full VEX document.</summary>
public string? DocumentUri { get; init; }
}
/// <summary>
/// Attestation summary for evidence panel.
/// </summary>
public sealed record AttestationSummaryDto
{
/// <summary>Attestation ID.</summary>
public required string Id { get; init; }
/// <summary>Predicate type.</summary>
public required string PredicateType { get; init; }
/// <summary>Subject digest.</summary>
public required string SubjectDigest { get; init; }
/// <summary>Signer identity.</summary>
public string? Signer { get; init; }
/// <summary>When signed.</summary>
public DateTimeOffset? SignedAt { get; init; }
/// <summary>Verification status.</summary>
public required string VerificationStatus { get; init; }
/// <summary>Transparency log entry if logged.</summary>
public string? TransparencyLogEntry { get; init; }
/// <summary>Link to full attestation.</summary>
public string? AttestationUri { get; init; }
}
/// <summary>
/// Delta evidence showing what changed.
/// </summary>
public sealed record DeltaEvidenceDto
{
/// <summary>Delta comparison ID.</summary>
public required string DeltaId { get; init; }
/// <summary>Previous scan ID.</summary>
public required string PreviousScanId { get; init; }
/// <summary>Current scan ID.</summary>
public required string CurrentScanId { get; init; }
/// <summary>When comparison was made.</summary>
public DateTimeOffset ComparedAt { get; init; }
/// <summary>Summary of changes.</summary>
public DeltaSummaryDto? Summary { get; init; }
/// <summary>Link to full delta report.</summary>
public string? DeltaReportUri { get; init; }
}
/// <summary>
/// Summary of delta changes.
/// </summary>
public sealed record DeltaSummaryDto
{
/// <summary>New findings.</summary>
public int AddedCount { get; init; }
/// <summary>Removed findings.</summary>
public int RemovedCount { get; init; }
/// <summary>Changed findings.</summary>
public int ChangedCount { get; init; }
/// <summary>Was this finding new in this scan?</summary>
public bool IsNew { get; init; }
/// <summary>Was this finding's status changed?</summary>
public bool StatusChanged { get; init; }
/// <summary>Previous status if changed.</summary>
public string? PreviousStatus { get; init; }
}
/// <summary>
/// Policy evaluation evidence.
/// </summary>
public sealed record PolicyEvidenceDto
{
/// <summary>Policy version used.</summary>
public required string PolicyVersion { get; init; }
/// <summary>Policy digest.</summary>
public required string PolicyDigest { get; init; }
/// <summary>Verdict from policy evaluation.</summary>
public required string Verdict { get; init; }
/// <summary>Rules that fired.</summary>
public IReadOnlyList<PolicyRuleFiredDto>? RulesFired { get; init; }
/// <summary>Counterfactuals - what would change the verdict.</summary>
public IReadOnlyList<string>? Counterfactuals { get; init; }
/// <summary>Link to policy document.</summary>
public string? PolicyDocumentUri { get; init; }
}
/// <summary>
/// Policy rule that fired during evaluation.
/// </summary>
public sealed record PolicyRuleFiredDto
{
/// <summary>Rule ID.</summary>
public required string RuleId { get; init; }
/// <summary>Rule name.</summary>
public required string Name { get; init; }
/// <summary>Effect (allow, deny, warn).</summary>
public required string Effect { get; init; }
/// <summary>Reason the rule fired.</summary>
public string? Reason { get; init; }
}
/// <summary>
/// Content-addressed manifest hashes for determinism verification.
/// </summary>
public sealed record ManifestHashesDto
{
/// <summary>Artifact digest (image or SBOM).</summary>
public required string ArtifactDigest { get; init; }
/// <summary>Run manifest hash.</summary>
public required string ManifestHash { get; init; }
/// <summary>Feed snapshot hash.</summary>
public required string FeedSnapshotHash { get; init; }
/// <summary>Policy hash.</summary>
public required string PolicyHash { get; init; }
/// <summary>Knowledge snapshot ID.</summary>
public string? KnowledgeSnapshotId { get; init; }
/// <summary>Graph revision ID.</summary>
public string? GraphRevisionId { get; init; }
}
/// <summary>
/// Overall verification status.
/// </summary>
public sealed record VerificationStatusDto
{
/// <summary>Overall status (verified, partial, failed, unknown).</summary>
public required string Status { get; init; }
/// <summary>True if all hashes match expected values.</summary>
public bool HashesVerified { get; init; }
/// <summary>True if attestations verify.</summary>
public bool AttestationsVerified { get; init; }
/// <summary>True if evidence is complete.</summary>
public bool EvidenceComplete { get; init; }
/// <summary>Any verification issues.</summary>
public IReadOnlyList<string>? Issues { get; init; }
/// <summary>Last verification timestamp.</summary>
public DateTimeOffset? VerifiedAt { get; init; }
}