- Introduced `sink-detect.js` with various security sink detection patterns categorized by type (e.g., command injection, SQL injection, file operations). - Implemented functions to build a lookup map for fast sink detection and to match sink calls against known patterns. - Added `package-lock.json` for dependency management.
270 lines
9.2 KiB
C#
270 lines
9.2 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// A comprehensive risk report that includes all explainability data for a finding.
|
|
/// </summary>
|
|
public sealed record RiskReport
|
|
{
|
|
/// <summary>Unique report identifier</summary>
|
|
public required string Id { get; init; }
|
|
|
|
/// <summary>The finding this report explains</summary>
|
|
public required string FindingId { get; init; }
|
|
|
|
/// <summary>The vulnerability ID (CVE, GHSA, etc.)</summary>
|
|
public required string VulnerabilityId { get; init; }
|
|
|
|
/// <summary>Package name</summary>
|
|
public required string PackageName { get; init; }
|
|
|
|
/// <summary>Package version</summary>
|
|
public required string PackageVersion { get; init; }
|
|
|
|
/// <summary>Assumptions made during analysis</summary>
|
|
public AssumptionSet? Assumptions { get; init; }
|
|
|
|
/// <summary>Falsifiability criteria and status</summary>
|
|
public FalsifiabilityCriteria? Falsifiability { get; init; }
|
|
|
|
/// <summary>Evidence density confidence score</summary>
|
|
public EvidenceDensityScore? ConfidenceScore { get; init; }
|
|
|
|
/// <summary>Human-readable explanation of the finding</summary>
|
|
public required string Explanation { get; init; }
|
|
|
|
/// <summary>Detailed narrative explaining the risk</summary>
|
|
public string? DetailedNarrative { get; init; }
|
|
|
|
/// <summary>Recommended actions</summary>
|
|
public ImmutableArray<RecommendedAction> RecommendedActions { get; init; } = [];
|
|
|
|
/// <summary>When this report was generated</summary>
|
|
public required DateTimeOffset GeneratedAt { get; init; }
|
|
|
|
/// <summary>Version of the explainability engine</summary>
|
|
public required string EngineVersion { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A recommended action to address a finding.
|
|
/// </summary>
|
|
/// <param name="Priority">Action priority (1 = highest)</param>
|
|
/// <param name="Action">The recommended action</param>
|
|
/// <param name="Rationale">Why this action is recommended</param>
|
|
/// <param name="Effort">Estimated effort level</param>
|
|
public sealed record RecommendedAction(
|
|
int Priority,
|
|
string Action,
|
|
string Rationale,
|
|
EffortLevel Effort
|
|
);
|
|
|
|
/// <summary>
|
|
/// Effort level for a recommended action.
|
|
/// </summary>
|
|
public enum EffortLevel
|
|
{
|
|
/// <summary>Quick configuration change or update</summary>
|
|
Low,
|
|
|
|
/// <summary>Moderate code changes or testing required</summary>
|
|
Medium,
|
|
|
|
/// <summary>Significant refactoring or architectural changes</summary>
|
|
High
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates comprehensive risk reports.
|
|
/// </summary>
|
|
public interface IRiskReportGenerator
|
|
{
|
|
/// <summary>
|
|
/// Generates a risk report for a finding.
|
|
/// </summary>
|
|
RiskReport Generate(RiskReportInput input);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Input for generating a risk report.
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default implementation of <see cref="IRiskReportGenerator"/>.
|
|
/// </summary>
|
|
public sealed class RiskReportGenerator : IRiskReportGenerator
|
|
{
|
|
private const string EngineVersionValue = "1.0.0";
|
|
|
|
private readonly IEvidenceDensityScorer _scorer;
|
|
|
|
public RiskReportGenerator(IEvidenceDensityScorer scorer)
|
|
{
|
|
_scorer = scorer;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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<string>
|
|
{
|
|
$"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<string>();
|
|
|
|
// 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<RecommendedAction> GenerateRecommendedActions(RiskReportInput input)
|
|
{
|
|
var actions = new List<RecommendedAction>();
|
|
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;
|
|
}
|
|
}
|