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:
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -187,7 +187,7 @@ internal sealed class DeltaScanRequestHandler : IDeltaScanRequestHandler
|
||||
_logger.LogInformation(
|
||||
"Delta scan triggered for DRIFT event {EventId}: scanId={ScanId}, created={Created}",
|
||||
runtimeEvent.EventId,
|
||||
result.Snapshot.Id,
|
||||
result.Snapshot.ScanId,
|
||||
result.Created);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Result of call graph ingestion.
|
||||
/// </summary>
|
||||
public sealed record CallGraphIngestionResult(
|
||||
string CallgraphId,
|
||||
int NodeCount,
|
||||
int EdgeCount,
|
||||
string Digest);
|
||||
|
||||
/// <summary>
|
||||
/// Service for ingesting call graphs.
|
||||
/// </summary>
|
||||
public interface ICallGraphIngestionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds an existing call graph by digest for idempotency checks.
|
||||
/// </summary>
|
||||
Task<ExistingCallGraphDto?> FindByDigestAsync(
|
||||
ScanId scanId,
|
||||
string contentDigest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Ingests a call graph for a scan.
|
||||
/// </summary>
|
||||
Task<CallGraphIngestionResult> IngestAsync(
|
||||
ScanId scanId,
|
||||
CallGraphV1Dto callGraph,
|
||||
string contentDigest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Validates a call graph before ingestion.
|
||||
/// </summary>
|
||||
CallGraphValidationResult Validate(CallGraphV1Dto callGraph);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of call graph validation.
|
||||
/// </summary>
|
||||
public sealed record CallGraphValidationResult(
|
||||
bool IsValid,
|
||||
IReadOnlyList<string>? Errors = null)
|
||||
{
|
||||
public static CallGraphValidationResult Success() => new(true);
|
||||
public static CallGraphValidationResult Failure(params string[] errors) => new(false, errors);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for exporting findings as SARIF.
|
||||
/// </summary>
|
||||
public interface ISarifExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports scan findings as a SARIF document.
|
||||
/// </summary>
|
||||
Task<object?> ExportAsync(ScanId scanId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service for exporting findings as CycloneDX with reachability extension.
|
||||
/// </summary>
|
||||
public interface ICycloneDxExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports scan findings as CycloneDX with reachability annotations.
|
||||
/// </summary>
|
||||
Task<object?> ExportWithReachabilityAsync(ScanId scanId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service for exporting VEX decisions as OpenVEX.
|
||||
/// </summary>
|
||||
public interface IOpenVexExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports VEX decisions for the scan as OpenVEX format.
|
||||
/// </summary>
|
||||
Task<object?> ExportAsync(ScanId scanId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Result of triggering reachability computation.
|
||||
/// </summary>
|
||||
public sealed record ComputeJobResult(
|
||||
string JobId,
|
||||
string Status,
|
||||
bool AlreadyInProgress,
|
||||
string? EstimatedDuration = null);
|
||||
|
||||
/// <summary>
|
||||
/// Service for triggering reachability computation.
|
||||
/// </summary>
|
||||
public interface IReachabilityComputeService
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers reachability computation for a scan.
|
||||
/// </summary>
|
||||
Task<ComputeJobResult> TriggerComputeAsync(
|
||||
ScanId scanId,
|
||||
bool forceRecompute,
|
||||
IReadOnlyList<string>? entrypoints,
|
||||
IReadOnlyList<string>? targets,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Explanation reason with code and impact.
|
||||
/// </summary>
|
||||
public sealed record ExplanationReason(
|
||||
string Code,
|
||||
string Description,
|
||||
double? Impact = null);
|
||||
|
||||
/// <summary>
|
||||
/// Static analysis evidence.
|
||||
/// </summary>
|
||||
public sealed record StaticAnalysisEvidence(
|
||||
string? CallgraphDigest = null,
|
||||
int? PathLength = null,
|
||||
IReadOnlyList<string>? EdgeTypes = null);
|
||||
|
||||
/// <summary>
|
||||
/// Runtime evidence.
|
||||
/// </summary>
|
||||
public sealed record RuntimeEvidence(
|
||||
bool Observed,
|
||||
int HitCount = 0,
|
||||
DateTimeOffset? LastObserved = null);
|
||||
|
||||
/// <summary>
|
||||
/// Policy evaluation result.
|
||||
/// </summary>
|
||||
public sealed record PolicyEvaluationEvidence(
|
||||
string? PolicyDigest = null,
|
||||
string? Verdict = null,
|
||||
string? VerdictReason = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evidence chain for explanation.
|
||||
/// </summary>
|
||||
public sealed record EvidenceChain(
|
||||
StaticAnalysisEvidence? StaticAnalysis = null,
|
||||
RuntimeEvidence? RuntimeEvidence = null,
|
||||
PolicyEvaluationEvidence? PolicyEvaluation = null);
|
||||
|
||||
/// <summary>
|
||||
/// Full reachability explanation.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityExplanation(
|
||||
string CveId,
|
||||
string Purl,
|
||||
string Status,
|
||||
double Confidence,
|
||||
string? LatticeState = null,
|
||||
IReadOnlyList<string>? PathWitness = null,
|
||||
IReadOnlyList<ExplanationReason>? Why = null,
|
||||
EvidenceChain? Evidence = null,
|
||||
string? SpineId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Service for explaining reachability decisions.
|
||||
/// </summary>
|
||||
public interface IReachabilityExplainService
|
||||
{
|
||||
/// <summary>
|
||||
/// Explains why a CVE affects a component.
|
||||
/// </summary>
|
||||
Task<ReachabilityExplanation?> ExplainAsync(
|
||||
ScanId scanId,
|
||||
string cveId,
|
||||
string purl,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Component reachability result.
|
||||
/// </summary>
|
||||
public sealed record ComponentReachability(
|
||||
string Purl,
|
||||
string Status,
|
||||
double Confidence,
|
||||
string? LatticeState = null,
|
||||
IReadOnlyList<string>? Why = null);
|
||||
|
||||
/// <summary>
|
||||
/// Reachability finding result.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityFinding(
|
||||
string CveId,
|
||||
string Purl,
|
||||
string Status,
|
||||
double Confidence,
|
||||
string? LatticeState = null,
|
||||
string? Severity = null,
|
||||
string? AffectedVersions = null);
|
||||
|
||||
/// <summary>
|
||||
/// Service for querying reachability results.
|
||||
/// </summary>
|
||||
public interface IReachabilityQueryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets component reachability results for a scan.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ComponentReachability>> GetComponentsAsync(
|
||||
ScanId scanId,
|
||||
string? purlFilter,
|
||||
string? statusFilter,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets vulnerability findings with reachability for a scan.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ReachabilityFinding>> GetFindingsAsync(
|
||||
ScanId scanId,
|
||||
string? cveFilter,
|
||||
string? statusFilter,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Result of SBOM ingestion.
|
||||
/// </summary>
|
||||
public sealed record SbomIngestionResult(
|
||||
string SbomId,
|
||||
string Format,
|
||||
int ComponentCount,
|
||||
string Digest);
|
||||
|
||||
/// <summary>
|
||||
/// Service for ingesting SBOMs (CycloneDX or SPDX).
|
||||
/// </summary>
|
||||
public interface ISbomIngestionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ingests an SBOM for a scan.
|
||||
/// </summary>
|
||||
Task<SbomIngestionResult> IngestAsync(
|
||||
ScanId scanId,
|
||||
JsonDocument sbomDocument,
|
||||
string format,
|
||||
string? contentDigest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Detects the SBOM format from the document.
|
||||
/// </summary>
|
||||
string? DetectFormat(JsonDocument sbomDocument);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an SBOM document.
|
||||
/// </summary>
|
||||
SbomValidationResult Validate(JsonDocument sbomDocument, string format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of SBOM validation.
|
||||
/// </summary>
|
||||
public sealed record SbomValidationResult(
|
||||
bool IsValid,
|
||||
IReadOnlyList<string>? Errors = null)
|
||||
{
|
||||
public static SbomValidationResult Success() => new(true);
|
||||
public static SbomValidationResult Failure(params string[] errors) => new(false, errors);
|
||||
}
|
||||
Reference in New Issue
Block a user