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:
StellaOps Bot
2025-12-22 23:21:21 +02:00
parent 3ba7157b00
commit 5146204f1b
529 changed files with 73579 additions and 5985 deletions

View File

@@ -0,0 +1,131 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) StellaOps
using System.Collections.Immutable;
namespace StellaOps.Scanner.Explainability.Falsifiability;
/// <summary>
/// Represents criteria that would falsify (disprove) a vulnerability finding.
/// Answers the question: "What would prove this finding wrong?"
/// </summary>
public sealed record FalsifiabilityCriteria
{
/// <summary>
/// Unique identifier for this criteria set.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// The finding ID these criteria apply to.
/// </summary>
public required string FindingId { get; init; }
/// <summary>
/// Individual criteria that would disprove the finding.
/// </summary>
public ImmutableArray<FalsificationCriterion> Criteria { get; init; } = [];
/// <summary>
/// Overall falsifiability status.
/// </summary>
public FalsifiabilityStatus Status { get; init; } = FalsifiabilityStatus.Unknown;
/// <summary>
/// Human-readable summary of what would disprove this finding.
/// </summary>
public string? Summary { get; init; }
/// <summary>
/// When these criteria were generated.
/// </summary>
public required DateTimeOffset GeneratedAt { get; init; }
}
/// <summary>
/// A single criterion that would falsify a finding.
/// </summary>
/// <param name="Type">The type of falsification check</param>
/// <param name="Description">Human-readable description of the criterion</param>
/// <param name="CheckExpression">Machine-evaluable expression (e.g., CEL, Rego)</param>
/// <param name="Evidence">Evidence that supports or refutes this criterion</param>
/// <param name="Status">Current evaluation status</param>
public sealed record FalsificationCriterion(
FalsificationType Type,
string Description,
string? CheckExpression,
string? Evidence,
CriterionStatus Status
);
/// <summary>
/// Types of falsification criteria.
/// </summary>
public enum FalsificationType
{
/// <summary>Package is not actually installed</summary>
PackageNotPresent,
/// <summary>Vulnerable version is not the installed version</summary>
VersionMismatch,
/// <summary>Vulnerable code path is not reachable</summary>
CodeUnreachable,
/// <summary>Required feature/function is disabled</summary>
FeatureDisabled,
/// <summary>Mitigation is in place (ASLR, stack canaries, etc.)</summary>
MitigationPresent,
/// <summary>Network exposure required but not present</summary>
NoNetworkExposure,
/// <summary>Required privileges not available</summary>
InsufficientPrivileges,
/// <summary>Vulnerability is already patched</summary>
PatchApplied,
/// <summary>Configuration prevents exploitation</summary>
ConfigurationPrevents,
/// <summary>Runtime environment prevents exploitation</summary>
RuntimePrevents
}
/// <summary>
/// Status of a falsification criterion evaluation.
/// </summary>
public enum CriterionStatus
{
/// <summary>Not yet evaluated</summary>
Pending,
/// <summary>Criterion is satisfied (finding is falsified)</summary>
Satisfied,
/// <summary>Criterion is not satisfied (finding stands)</summary>
NotSatisfied,
/// <summary>Could not be evaluated (insufficient data)</summary>
Inconclusive
}
/// <summary>
/// Overall falsifiability status.
/// </summary>
public enum FalsifiabilityStatus
{
/// <summary>Status not determined</summary>
Unknown,
/// <summary>Finding has been falsified (at least one criterion satisfied)</summary>
Falsified,
/// <summary>Finding stands (all criteria not satisfied)</summary>
NotFalsified,
/// <summary>Some criteria inconclusive</summary>
PartiallyEvaluated
}

View File

@@ -0,0 +1,215 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) StellaOps
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Explainability.Assumptions;
namespace StellaOps.Scanner.Explainability.Falsifiability;
/// <summary>
/// Input data for generating falsifiability criteria.
/// </summary>
public sealed record FalsifiabilityInput
{
/// <summary>The finding ID</summary>
public required string FindingId { get; init; }
/// <summary>The CVE or vulnerability ID</summary>
public required string VulnerabilityId { get; init; }
/// <summary>Package name</summary>
public required string PackageName { get; init; }
/// <summary>Installed version</summary>
public required string InstalledVersion { get; init; }
/// <summary>Vulnerable version range</summary>
public string? VulnerableRange { get; init; }
/// <summary>Fixed version, if available</summary>
public string? FixedVersion { get; init; }
/// <summary>Assumptions made during analysis</summary>
public AssumptionSet? Assumptions { get; init; }
/// <summary>Whether reachability analysis was performed</summary>
public bool HasReachabilityData { get; init; }
/// <summary>Whether code is reachable (if analysis was performed)</summary>
public bool? IsReachable { get; init; }
/// <summary>Known mitigations in place</summary>
public ImmutableArray<string> Mitigations { get; init; } = [];
}
/// <summary>
/// Generates falsifiability criteria for vulnerability findings.
/// </summary>
public interface IFalsifiabilityGenerator
{
/// <summary>
/// Generates falsifiability criteria for a finding.
/// </summary>
FalsifiabilityCriteria Generate(FalsifiabilityInput input);
}
/// <summary>
/// Default implementation of <see cref="IFalsifiabilityGenerator"/>.
/// </summary>
public sealed class FalsifiabilityGenerator : IFalsifiabilityGenerator
{
private readonly ILogger<FalsifiabilityGenerator> _logger;
public FalsifiabilityGenerator(ILogger<FalsifiabilityGenerator> logger)
{
_logger = logger;
}
/// <inheritdoc />
public FalsifiabilityCriteria Generate(FalsifiabilityInput input)
{
_logger.LogDebug("Generating falsifiability criteria for finding {FindingId}", input.FindingId);
var criteria = new List<FalsificationCriterion>();
// Criterion 1: Package presence
criteria.Add(new FalsificationCriterion(
FalsificationType.PackageNotPresent,
$"Package '{input.PackageName}' is not actually installed or is a false positive from manifest parsing",
$"package.exists(\"{input.PackageName}\") == false",
null,
CriterionStatus.Pending));
// Criterion 2: Version mismatch
if (input.VulnerableRange is not null)
{
criteria.Add(new FalsificationCriterion(
FalsificationType.VersionMismatch,
$"Installed version '{input.InstalledVersion}' is not within vulnerable range '{input.VulnerableRange}'",
$"version.inRange(\"{input.InstalledVersion}\", \"{input.VulnerableRange}\") == false",
null,
CriterionStatus.Pending));
}
// Criterion 3: Patch applied
if (input.FixedVersion is not null)
{
criteria.Add(new FalsificationCriterion(
FalsificationType.PatchApplied,
$"Version '{input.InstalledVersion}' is at or above fixed version '{input.FixedVersion}'",
$"version.gte(\"{input.InstalledVersion}\", \"{input.FixedVersion}\")",
null,
CriterionStatus.Pending));
}
// Criterion 4: Code unreachable
if (input.HasReachabilityData)
{
var reachabilityStatus = input.IsReachable switch
{
false => CriterionStatus.Satisfied,
true => CriterionStatus.NotSatisfied,
null => CriterionStatus.Inconclusive
};
criteria.Add(new FalsificationCriterion(
FalsificationType.CodeUnreachable,
"Vulnerable code path is not reachable from application entry points",
"reachability.isReachable() == false",
input.IsReachable.HasValue ? $"Reachability analysis: {(input.IsReachable.Value ? "reachable" : "unreachable")}" : null,
reachabilityStatus));
}
// Criterion 5: Mitigations
foreach (var mitigation in input.Mitigations)
{
criteria.Add(new FalsificationCriterion(
FalsificationType.MitigationPresent,
$"Mitigation '{mitigation}' prevents exploitation",
null,
$"Mitigation present: {mitigation}",
CriterionStatus.Satisfied));
}
// Criterion 6: Assumption-based criteria
if (input.Assumptions is not null)
{
foreach (var assumption in input.Assumptions.Assumptions.Where(a => a.IsContradicted))
{
var type = assumption.Category switch
{
AssumptionCategory.NetworkExposure => FalsificationType.NoNetworkExposure,
AssumptionCategory.ProcessPrivilege => FalsificationType.InsufficientPrivileges,
AssumptionCategory.FeatureGate => FalsificationType.FeatureDisabled,
AssumptionCategory.RuntimeConfig => FalsificationType.ConfigurationPrevents,
AssumptionCategory.CompilerFlag => FalsificationType.MitigationPresent,
_ => FalsificationType.RuntimePrevents
};
criteria.Add(new FalsificationCriterion(
type,
$"Assumption '{assumption.Key}' was contradicted: assumed '{assumption.AssumedValue}', observed '{assumption.ObservedValue}'",
null,
$"Observed: {assumption.ObservedValue}",
CriterionStatus.Satisfied));
}
}
// Determine overall status
var status = DetermineOverallStatus(criteria);
// Generate summary
var summary = GenerateSummary(input, criteria, status);
return new FalsifiabilityCriteria
{
Id = Guid.NewGuid().ToString("N"),
FindingId = input.FindingId,
Criteria = [.. criteria],
Status = status,
Summary = summary,
GeneratedAt = DateTimeOffset.UtcNow
};
}
private static FalsifiabilityStatus DetermineOverallStatus(List<FalsificationCriterion> criteria)
{
if (criteria.Count == 0)
return FalsifiabilityStatus.Unknown;
if (criteria.Any(c => c.Status == CriterionStatus.Satisfied))
return FalsifiabilityStatus.Falsified;
if (criteria.All(c => c.Status == CriterionStatus.NotSatisfied))
return FalsifiabilityStatus.NotFalsified;
if (criteria.Any(c => c.Status is CriterionStatus.Pending or CriterionStatus.Inconclusive))
return FalsifiabilityStatus.PartiallyEvaluated;
return FalsifiabilityStatus.Unknown;
}
private static string GenerateSummary(
FalsifiabilityInput input,
List<FalsificationCriterion> criteria,
FalsifiabilityStatus status)
{
return status switch
{
FalsifiabilityStatus.Falsified =>
$"Finding {input.FindingId} can be falsified. " +
$"Criteria satisfied: {string.Join(", ", criteria.Where(c => c.Status == CriterionStatus.Satisfied).Select(c => c.Type))}",
FalsifiabilityStatus.NotFalsified =>
$"Finding {input.FindingId} has not been falsified. All {criteria.Count} criteria evaluated negative.",
FalsifiabilityStatus.PartiallyEvaluated =>
$"Finding {input.FindingId} is partially evaluated. " +
$"{criteria.Count(c => c.Status == CriterionStatus.Pending)} pending, " +
$"{criteria.Count(c => c.Status == CriterionStatus.Inconclusive)} inconclusive.",
_ => $"Finding {input.FindingId} falsifiability status unknown."
};
}
}