feat: add security sink detection patterns for JavaScript/TypeScript
- 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.
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user