// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (c) StellaOps using System.Collections.Immutable; using StellaOps.Scanner.Explainability.Assumptions; using StellaOps.Scanner.Explainability.Confidence; using StellaOps.Scanner.Explainability.Falsifiability; namespace StellaOps.Scanner.Explainability; /// /// A comprehensive risk report that includes all explainability data for a finding. /// public sealed record RiskReport { /// Unique report identifier public required string Id { get; init; } /// The finding this report explains public required string FindingId { get; init; } /// The vulnerability ID (CVE, GHSA, etc.) public required string VulnerabilityId { get; init; } /// Package name public required string PackageName { get; init; } /// Package version public required string PackageVersion { get; init; } /// Assumptions made during analysis public AssumptionSet? Assumptions { get; init; } /// Falsifiability criteria and status public FalsifiabilityCriteria? Falsifiability { get; init; } /// Evidence density confidence score public EvidenceDensityScore? ConfidenceScore { get; init; } /// Human-readable explanation of the finding public required string Explanation { get; init; } /// Detailed narrative explaining the risk public string? DetailedNarrative { get; init; } /// Recommended actions public ImmutableArray RecommendedActions { get; init; } = []; /// When this report was generated public required DateTimeOffset GeneratedAt { get; init; } /// Version of the explainability engine public required string EngineVersion { get; init; } } /// /// A recommended action to address a finding. /// /// Action priority (1 = highest) /// The recommended action /// Why this action is recommended /// Estimated effort level public sealed record RecommendedAction( int Priority, string Action, string Rationale, EffortLevel Effort ); /// /// Effort level for a recommended action. /// public enum EffortLevel { /// Quick configuration change or update Low, /// Moderate code changes or testing required Medium, /// Significant refactoring or architectural changes High } /// /// Generates comprehensive risk reports. /// public interface IRiskReportGenerator { /// /// Generates a risk report for a finding. /// RiskReport Generate(RiskReportInput input); } /// /// Input for generating a risk report. /// public sealed record RiskReportInput { public required string FindingId { get; init; } public required string VulnerabilityId { get; init; } public required string PackageName { get; init; } public required string PackageVersion { get; init; } public string? Severity { get; init; } public string? Description { get; init; } public string? FixedVersion { get; init; } public AssumptionSet? Assumptions { get; init; } public FalsifiabilityCriteria? Falsifiability { get; init; } public EvidenceFactors? EvidenceFactors { get; init; } } /// /// Default implementation of . /// public sealed class RiskReportGenerator : IRiskReportGenerator { private const string EngineVersionValue = "1.0.0"; private readonly IEvidenceDensityScorer _scorer; public RiskReportGenerator(IEvidenceDensityScorer scorer) { _scorer = scorer; } /// public RiskReport Generate(RiskReportInput input) { // Calculate confidence score if evidence factors provided EvidenceDensityScore? confidenceScore = null; if (input.EvidenceFactors is not null) { confidenceScore = _scorer.Calculate(input.EvidenceFactors); } var explanation = GenerateExplanation(input); var narrative = GenerateNarrative(input, confidenceScore); var actions = GenerateRecommendedActions(input); return new RiskReport { Id = Guid.NewGuid().ToString("N"), FindingId = input.FindingId, VulnerabilityId = input.VulnerabilityId, PackageName = input.PackageName, PackageVersion = input.PackageVersion, Assumptions = input.Assumptions, Falsifiability = input.Falsifiability, ConfidenceScore = confidenceScore, Explanation = explanation, DetailedNarrative = narrative, RecommendedActions = [.. actions], GeneratedAt = DateTimeOffset.UtcNow, EngineVersion = EngineVersionValue }; } private static string GenerateExplanation(RiskReportInput input) { var parts = new List { $"Vulnerability {input.VulnerabilityId} affects {input.PackageName}@{input.PackageVersion}." }; if (input.Severity is not null) { parts.Add($"Severity: {input.Severity}."); } if (input.Falsifiability?.Status == FalsifiabilityStatus.Falsified) { parts.Add("This finding has been falsified and may not be exploitable in your environment."); } else if (input.Assumptions?.HasContradictions == true) { parts.Add("Some analysis assumptions have been contradicted by observed evidence."); } return string.Join(" ", parts); } private static string GenerateNarrative(RiskReportInput input, EvidenceDensityScore? score) { var sections = new List(); // Overview sections.Add($"## Overview\n{input.Description ?? "No description available."}"); // Assumptions section if (input.Assumptions is not null && input.Assumptions.Assumptions.Length > 0) { var assumptionLines = input.Assumptions.Assumptions .Select(a => $"- **{a.Category}**: {a.Key} = {a.AssumedValue}" + (a.ObservedValue is not null ? $" (observed: {a.ObservedValue})" : "")); sections.Add($"## Assumptions\n{string.Join("\n", assumptionLines)}"); } // Falsifiability section if (input.Falsifiability is not null) { sections.Add($"## Falsifiability\n**Status**: {input.Falsifiability.Status}\n\n{input.Falsifiability.Summary}"); } // Confidence section if (score is not null) { sections.Add($"## Confidence Assessment\n{score.Explanation}"); if (score.ImprovementRecommendations.Count > 0) { var recs = score.ImprovementRecommendations.Select(r => $"- {r}"); sections.Add($"### Recommendations to Improve Confidence\n{string.Join("\n", recs)}"); } } return string.Join("\n\n", sections); } private static List GenerateRecommendedActions(RiskReportInput input) { var actions = new List(); int priority = 1; // Action: Update package if fix available if (input.FixedVersion is not null) { actions.Add(new RecommendedAction( priority++, $"Update {input.PackageName} to version {input.FixedVersion} or later", "A fixed version is available that addresses this vulnerability", EffortLevel.Low)); } // Action: Validate assumptions if (input.Assumptions is not null && input.Assumptions.ValidatedCount < input.Assumptions.Assumptions.Length) { actions.Add(new RecommendedAction( priority++, "Validate analysis assumptions with runtime observations", $"Only {input.Assumptions.ValidatedCount}/{input.Assumptions.Assumptions.Length} assumptions are validated", EffortLevel.Medium)); } // Action: Evaluate falsifiability criteria if (input.Falsifiability?.Status == FalsifiabilityStatus.PartiallyEvaluated) { var pendingCount = input.Falsifiability.Criteria.Count(c => c.Status == CriterionStatus.Pending); actions.Add(new RecommendedAction( priority++, "Complete falsifiability evaluation", $"{pendingCount} criteria are pending evaluation", EffortLevel.Medium)); } // Default action if no fix available if (input.FixedVersion is null) { actions.Add(new RecommendedAction( priority, "Monitor for vendor patch or implement compensating controls", "No fixed version is currently available", EffortLevel.High)); } return actions; } }