Refactor SurfaceCacheValidator to simplify oldest entry calculation

Add global using for Xunit in test project

Enhance ImportValidatorTests with async validation and quarantine checks

Implement FileSystemQuarantineServiceTests for quarantine functionality

Add integration tests for ImportValidator to check monotonicity

Create BundleVersionTests to validate version parsing and comparison logic

Implement VersionMonotonicityCheckerTests for monotonicity checks and activation logic
This commit is contained in:
master
2025-12-16 10:44:00 +02:00
parent b1f40945b7
commit 4391f35d8a
107 changed files with 10844 additions and 287 deletions

View File

@@ -0,0 +1,69 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Call graph submission request (CallGraphV1 schema).
/// </summary>
public sealed record CallGraphV1Dto(
[property: JsonPropertyName("schema")] string Schema,
[property: JsonPropertyName("scanKey")] string ScanKey,
[property: JsonPropertyName("language")] string Language,
[property: JsonPropertyName("nodes")] IReadOnlyList<CallGraphNodeDto> Nodes,
[property: JsonPropertyName("edges")] IReadOnlyList<CallGraphEdgeDto> Edges,
[property: JsonPropertyName("artifacts")] IReadOnlyList<CallGraphArtifactDto>? Artifacts = null,
[property: JsonPropertyName("entrypoints")] IReadOnlyList<CallGraphEntrypointDto>? Entrypoints = null);
/// <summary>
/// Artifact in a call graph.
/// </summary>
public sealed record CallGraphArtifactDto(
[property: JsonPropertyName("artifactKey")] string ArtifactKey,
[property: JsonPropertyName("kind")] string? Kind = null,
[property: JsonPropertyName("sha256")] string? Sha256 = null);
/// <summary>
/// Node in a call graph.
/// </summary>
public sealed record CallGraphNodeDto(
[property: JsonPropertyName("nodeId")] string NodeId,
[property: JsonPropertyName("symbolKey")] string SymbolKey,
[property: JsonPropertyName("artifactKey")] string? ArtifactKey = null,
[property: JsonPropertyName("visibility")] string? Visibility = null,
[property: JsonPropertyName("isEntrypointCandidate")] bool IsEntrypointCandidate = false);
/// <summary>
/// Edge in a call graph.
/// </summary>
public sealed record CallGraphEdgeDto(
[property: JsonPropertyName("from")] string From,
[property: JsonPropertyName("to")] string To,
[property: JsonPropertyName("kind")] string Kind = "static",
[property: JsonPropertyName("reason")] string? Reason = null,
[property: JsonPropertyName("weight")] double Weight = 1.0);
/// <summary>
/// Entrypoint in a call graph.
/// </summary>
public sealed record CallGraphEntrypointDto(
[property: JsonPropertyName("nodeId")] string NodeId,
[property: JsonPropertyName("kind")] string Kind,
[property: JsonPropertyName("route")] string? Route = null,
[property: JsonPropertyName("framework")] string? Framework = null);
/// <summary>
/// Response when call graph is accepted.
/// </summary>
public sealed record CallGraphAcceptedResponseDto(
[property: JsonPropertyName("callgraphId")] string CallgraphId,
[property: JsonPropertyName("nodeCount")] int NodeCount,
[property: JsonPropertyName("edgeCount")] int EdgeCount,
[property: JsonPropertyName("digest")] string Digest);
/// <summary>
/// Existing call graph reference (for duplicate detection).
/// </summary>
public sealed record ExistingCallGraphDto(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("digest")] string Digest,
[property: JsonPropertyName("createdAt")] DateTimeOffset CreatedAt);

View File

@@ -129,10 +129,22 @@ public sealed record DsseEnvelopeDto
public sealed record DsseSignatureDto
{
[JsonPropertyName("keyid")]
[JsonPropertyOrder(0)]
public string KeyId { get; init; } = string.Empty;
[JsonPropertyName("sig")]
[JsonPropertyOrder(1)]
public string Sig { get; init; } = string.Empty;
[JsonPropertyName("algorithm")]
[JsonPropertyOrder(2)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Algorithm { get; init; }
[JsonPropertyName("signature")]
[JsonPropertyOrder(3)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Signature { get; init; }
}
public sealed record ProofSpineVerificationDto

View File

@@ -0,0 +1,109 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Request to trigger reachability computation.
/// </summary>
public sealed record ComputeReachabilityRequestDto(
[property: JsonPropertyName("forceRecompute")] bool ForceRecompute = false,
[property: JsonPropertyName("entrypoints")] IReadOnlyList<string>? Entrypoints = null,
[property: JsonPropertyName("targets")] IReadOnlyList<string>? Targets = null);
/// <summary>
/// Response from triggering reachability computation.
/// </summary>
public sealed record ComputeReachabilityResponseDto(
[property: JsonPropertyName("jobId")] string JobId,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("estimatedDuration")] string? EstimatedDuration = null);
/// <summary>
/// Component reachability status.
/// </summary>
public sealed record ComponentReachabilityDto(
[property: JsonPropertyName("purl")] string Purl,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("confidence")] double Confidence,
[property: JsonPropertyName("latticeState")] string? LatticeState = null,
[property: JsonPropertyName("why")] IReadOnlyList<string>? Why = null);
/// <summary>
/// List of component reachability results.
/// </summary>
public sealed record ComponentReachabilityListDto(
[property: JsonPropertyName("items")] IReadOnlyList<ComponentReachabilityDto> Items,
[property: JsonPropertyName("total")] int Total);
/// <summary>
/// Vulnerability finding with reachability.
/// </summary>
public sealed record ReachabilityFindingDto(
[property: JsonPropertyName("cveId")] string CveId,
[property: JsonPropertyName("purl")] string Purl,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("confidence")] double Confidence,
[property: JsonPropertyName("latticeState")] string? LatticeState = null,
[property: JsonPropertyName("severity")] string? Severity = null,
[property: JsonPropertyName("affectedVersions")] string? AffectedVersions = null);
/// <summary>
/// List of reachability findings.
/// </summary>
public sealed record ReachabilityFindingListDto(
[property: JsonPropertyName("items")] IReadOnlyList<ReachabilityFindingDto> Items,
[property: JsonPropertyName("total")] int Total);
/// <summary>
/// Explanation reason with code and impact.
/// </summary>
public sealed record ExplanationReasonDto(
[property: JsonPropertyName("code")] string Code,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("impact")] double? Impact = null);
/// <summary>
/// Static analysis evidence.
/// </summary>
public sealed record StaticAnalysisEvidenceDto(
[property: JsonPropertyName("callgraphDigest")] string? CallgraphDigest = null,
[property: JsonPropertyName("pathLength")] int? PathLength = null,
[property: JsonPropertyName("edgeTypes")] IReadOnlyList<string>? EdgeTypes = null);
/// <summary>
/// Runtime evidence.
/// </summary>
public sealed record RuntimeEvidenceDto(
[property: JsonPropertyName("observed")] bool Observed,
[property: JsonPropertyName("hitCount")] int HitCount = 0,
[property: JsonPropertyName("lastObserved")] DateTimeOffset? LastObserved = null);
/// <summary>
/// Policy evaluation result.
/// </summary>
public sealed record PolicyEvaluationEvidenceDto(
[property: JsonPropertyName("policyDigest")] string? PolicyDigest = null,
[property: JsonPropertyName("verdict")] string? Verdict = null,
[property: JsonPropertyName("verdictReason")] string? VerdictReason = null);
/// <summary>
/// Evidence chain for explanation.
/// </summary>
public sealed record EvidenceChainDto(
[property: JsonPropertyName("staticAnalysis")] StaticAnalysisEvidenceDto? StaticAnalysis = null,
[property: JsonPropertyName("runtimeEvidence")] RuntimeEvidenceDto? RuntimeEvidence = null,
[property: JsonPropertyName("policyEvaluation")] PolicyEvaluationEvidenceDto? PolicyEvaluation = null);
/// <summary>
/// Full reachability explanation.
/// </summary>
public sealed record ReachabilityExplanationDto(
[property: JsonPropertyName("cveId")] string CveId,
[property: JsonPropertyName("purl")] string Purl,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("confidence")] double Confidence,
[property: JsonPropertyName("latticeState")] string? LatticeState = null,
[property: JsonPropertyName("pathWitness")] IReadOnlyList<string>? PathWitness = null,
[property: JsonPropertyName("why")] IReadOnlyList<ExplanationReasonDto>? Why = null,
[property: JsonPropertyName("evidence")] EvidenceChainDto? Evidence = null,
[property: JsonPropertyName("spineId")] string? SpineId = null);

View File

@@ -102,30 +102,3 @@ public sealed record ReportSummaryDto
[JsonPropertyOrder(4)]
public int Quieted { get; init; }
}
public sealed record DsseEnvelopeDto
{
[JsonPropertyName("payloadType")]
[JsonPropertyOrder(0)]
public string PayloadType { get; init; } = string.Empty;
[JsonPropertyName("payload")]
[JsonPropertyOrder(1)]
public string Payload { get; init; } = string.Empty;
[JsonPropertyName("signatures")]
[JsonPropertyOrder(2)]
public IReadOnlyList<DsseSignatureDto> Signatures { get; init; } = Array.Empty<DsseSignatureDto>();
}
public sealed record DsseSignatureDto
{
[JsonPropertyName("keyId")]
public string KeyId { get; init; } = string.Empty;
[JsonPropertyName("algorithm")]
public string Algorithm { get; init; } = string.Empty;
[JsonPropertyName("signature")]
public string Signature { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.WebService.Contracts;
/// <summary>
/// Response when SBOM is accepted.
/// </summary>
public sealed record SbomAcceptedResponseDto(
[property: JsonPropertyName("sbomId")] string SbomId,
[property: JsonPropertyName("format")] string Format,
[property: JsonPropertyName("componentCount")] int ComponentCount,
[property: JsonPropertyName("digest")] string Digest);
/// <summary>
/// SBOM format types.
/// </summary>
public static class SbomFormats
{
public const string CycloneDx = "cyclonedx";
public const string Spdx = "spdx";
}