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,108 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) StellaOps
namespace StellaOps.Scanner.Explainability.Assumptions;
/// <summary>
/// Represents a single assumption made during vulnerability analysis.
/// Assumptions capture the conditions under which a finding is considered valid.
/// </summary>
/// <param name="Category">The category of assumption (compiler flag, runtime config, etc.)</param>
/// <param name="Key">The specific assumption key (e.g., "-fstack-protector", "DEBUG_MODE")</param>
/// <param name="AssumedValue">The value assumed during analysis</param>
/// <param name="ObservedValue">The actual observed value, if available</param>
/// <param name="Source">How the assumption was derived</param>
/// <param name="Confidence">The confidence level in this assumption</param>
public sealed record Assumption(
AssumptionCategory Category,
string Key,
string AssumedValue,
string? ObservedValue,
AssumptionSource Source,
ConfidenceLevel Confidence
)
{
/// <summary>
/// Returns true if the observed value matches the assumed value.
/// </summary>
public bool IsValidated => ObservedValue is not null &&
string.Equals(AssumedValue, ObservedValue, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Returns true if the observed value contradicts the assumed value.
/// </summary>
public bool IsContradicted => ObservedValue is not null &&
!string.Equals(AssumedValue, ObservedValue, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Categories of assumptions that affect vulnerability exploitability.
/// </summary>
public enum AssumptionCategory
{
/// <summary>Compiler flags like -fstack-protector, -D_FORTIFY_SOURCE</summary>
CompilerFlag,
/// <summary>Environment variables, config files, runtime settings</summary>
RuntimeConfig,
/// <summary>Feature flags, build variants, conditional compilation</summary>
FeatureGate,
/// <summary>LD_PRELOAD, RPATH, symbol versioning, loader behavior</summary>
LoaderBehavior,
/// <summary>Port bindings, firewall rules, network exposure</summary>
NetworkExposure,
/// <summary>Capabilities, seccomp profiles, AppArmor/SELinux policies</summary>
ProcessPrivilege,
/// <summary>Memory layout assumptions (ASLR, PIE)</summary>
MemoryProtection,
/// <summary>System call availability and filtering</summary>
SyscallAvailability
}
/// <summary>
/// How an assumption was derived.
/// </summary>
public enum AssumptionSource
{
/// <summary>Default assumption when no evidence available</summary>
Default,
/// <summary>Inferred from static analysis of binaries/code</summary>
StaticAnalysis,
/// <summary>Observed from runtime telemetry</summary>
RuntimeObservation,
/// <summary>Derived from container/image manifest</summary>
ImageManifest,
/// <summary>Provided by user configuration</summary>
UserProvided,
/// <summary>Extracted from Dockerfile or build configuration</summary>
BuildConfig
}
/// <summary>
/// Confidence level in an assumption.
/// </summary>
public enum ConfidenceLevel
{
/// <summary>No evidence, using defaults</summary>
Low = 1,
/// <summary>Some indirect evidence</summary>
Medium = 2,
/// <summary>Strong evidence from static analysis</summary>
High = 3,
/// <summary>Direct runtime observation</summary>
Verified = 4
}

View File

@@ -0,0 +1,119 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) StellaOps
using System.Collections.Immutable;
namespace StellaOps.Scanner.Explainability.Assumptions;
/// <summary>
/// A collection of assumptions associated with a finding or analysis context.
/// Provides methods for querying and validating assumptions.
/// </summary>
public sealed record AssumptionSet
{
/// <summary>
/// The unique identifier for this assumption set.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// The assumptions in this set, keyed by category and key.
/// </summary>
public ImmutableArray<Assumption> Assumptions { get; init; } = [];
/// <summary>
/// When this assumption set was created.
/// </summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// Optional context identifier (e.g., finding ID, image digest).
/// </summary>
public string? ContextId { get; init; }
/// <summary>
/// Gets all assumptions of a specific category.
/// </summary>
public IEnumerable<Assumption> GetByCategory(AssumptionCategory category) =>
Assumptions.Where(a => a.Category == category);
/// <summary>
/// Gets a specific assumption by category and key.
/// </summary>
public Assumption? Get(AssumptionCategory category, string key) =>
Assumptions.FirstOrDefault(a => a.Category == category &&
string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase));
/// <summary>
/// Returns the overall confidence level (minimum of all assumptions).
/// </summary>
public ConfidenceLevel OverallConfidence =>
Assumptions.Length == 0
? ConfidenceLevel.Low
: Assumptions.Min(a => a.Confidence);
/// <summary>
/// Returns the count of validated assumptions.
/// </summary>
public int ValidatedCount => Assumptions.Count(a => a.IsValidated);
/// <summary>
/// Returns the count of contradicted assumptions.
/// </summary>
public int ContradictedCount => Assumptions.Count(a => a.IsContradicted);
/// <summary>
/// Returns true if any assumption is contradicted by observed evidence.
/// </summary>
public bool HasContradictions => Assumptions.Any(a => a.IsContradicted);
/// <summary>
/// Returns the validation ratio (validated / total with observations).
/// </summary>
public double ValidationRatio
{
get
{
var withObservations = Assumptions.Count(a => a.ObservedValue is not null);
return withObservations == 0 ? 0.0 : (double)ValidatedCount / withObservations;
}
}
/// <summary>
/// Creates a new AssumptionSet with an additional assumption.
/// </summary>
public AssumptionSet WithAssumption(Assumption assumption) =>
this with { Assumptions = Assumptions.Add(assumption) };
/// <summary>
/// Creates a new AssumptionSet with updated observation for an assumption.
/// </summary>
public AssumptionSet WithObservation(AssumptionCategory category, string key, string observedValue)
{
var index = Assumptions.FindIndex(a =>
a.Category == category &&
string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase));
if (index < 0)
return this;
var updated = Assumptions[index] with { ObservedValue = observedValue };
return this with { Assumptions = Assumptions.SetItem(index, updated) };
}
}
/// <summary>
/// Extension methods for ImmutableArray to support FindIndex.
/// </summary>
internal static class ImmutableArrayExtensions
{
public static int FindIndex<T>(this ImmutableArray<T> array, Func<T, bool> predicate)
{
for (int i = 0; i < array.Length; i++)
{
if (predicate(array[i]))
return i;
}
return -1;
}
}

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) StellaOps
namespace StellaOps.Scanner.Explainability.Assumptions;
/// <summary>
/// Collects assumptions from various sources during vulnerability analysis.
/// </summary>
public interface IAssumptionCollector
{
/// <summary>
/// Records an assumption made during analysis.
/// </summary>
/// <param name="category">The category of assumption</param>
/// <param name="key">The assumption key</param>
/// <param name="assumedValue">The assumed value</param>
/// <param name="source">How the assumption was derived</param>
/// <param name="confidence">Confidence level</param>
void Record(
AssumptionCategory category,
string key,
string assumedValue,
AssumptionSource source,
ConfidenceLevel confidence = ConfidenceLevel.Low);
/// <summary>
/// Records an observation that validates or contradicts an assumption.
/// </summary>
/// <param name="category">The category of assumption</param>
/// <param name="key">The assumption key</param>
/// <param name="observedValue">The observed value</param>
void RecordObservation(AssumptionCategory category, string key, string observedValue);
/// <summary>
/// Builds the final assumption set from collected assumptions.
/// </summary>
/// <param name="contextId">Optional context identifier</param>
/// <returns>The completed assumption set</returns>
AssumptionSet Build(string? contextId = null);
/// <summary>
/// Clears all collected assumptions.
/// </summary>
void Clear();
}
/// <summary>
/// Default implementation of <see cref="IAssumptionCollector"/>.
/// </summary>
public sealed class AssumptionCollector : IAssumptionCollector
{
private readonly Dictionary<(AssumptionCategory, string), Assumption> _assumptions = new();
/// <inheritdoc />
public void Record(
AssumptionCategory category,
string key,
string assumedValue,
AssumptionSource source,
ConfidenceLevel confidence = ConfidenceLevel.Low)
{
var normalizedKey = key.ToLowerInvariant();
var existing = _assumptions.GetValueOrDefault((category, normalizedKey));
// Keep assumption with higher confidence
if (existing is null || confidence > existing.Confidence)
{
_assumptions[(category, normalizedKey)] = new Assumption(
category,
key,
assumedValue,
existing?.ObservedValue,
source,
confidence);
}
}
/// <inheritdoc />
public void RecordObservation(AssumptionCategory category, string key, string observedValue)
{
var normalizedKey = key.ToLowerInvariant();
if (_assumptions.TryGetValue((category, normalizedKey), out var existing))
{
_assumptions[(category, normalizedKey)] = existing with
{
ObservedValue = observedValue,
Confidence = ConfidenceLevel.Verified
};
}
else
{
// Record observation even without prior assumption
_assumptions[(category, normalizedKey)] = new Assumption(
category,
key,
observedValue, // Use observed as assumed when no prior assumption
observedValue,
AssumptionSource.RuntimeObservation,
ConfidenceLevel.Verified);
}
}
/// <inheritdoc />
public AssumptionSet Build(string? contextId = null)
{
return new AssumptionSet
{
Id = Guid.NewGuid().ToString("N"),
Assumptions = [.. _assumptions.Values],
CreatedAt = DateTimeOffset.UtcNow,
ContextId = contextId
};
}
/// <inheritdoc />
public void Clear() => _assumptions.Clear();
}