Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Scanner.Orchestration.Fidelity;
|
||||
|
||||
public interface IFidelityAwareAnalyzer
|
||||
{
|
||||
Task<FidelityAnalysisResult> AnalyzeAsync(
|
||||
AnalysisRequest request,
|
||||
FidelityLevel level,
|
||||
CancellationToken ct);
|
||||
|
||||
Task<FidelityUpgradeResult> UpgradeFidelityAsync(
|
||||
Guid findingId,
|
||||
FidelityLevel targetLevel,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public sealed class FidelityAwareAnalyzer : IFidelityAwareAnalyzer
|
||||
{
|
||||
private readonly ICallGraphExtractor _callGraphExtractor;
|
||||
private readonly IRuntimeCorrelator _runtimeCorrelator;
|
||||
private readonly IBinaryMapper _binaryMapper;
|
||||
private readonly IPackageMatcher _packageMatcher;
|
||||
private readonly IAnalysisRepository _repository;
|
||||
private readonly ILogger<FidelityAwareAnalyzer> _logger;
|
||||
|
||||
public FidelityAwareAnalyzer(
|
||||
ICallGraphExtractor callGraphExtractor,
|
||||
IRuntimeCorrelator runtimeCorrelator,
|
||||
IBinaryMapper binaryMapper,
|
||||
IPackageMatcher packageMatcher,
|
||||
IAnalysisRepository repository,
|
||||
ILogger<FidelityAwareAnalyzer> logger)
|
||||
{
|
||||
_callGraphExtractor = callGraphExtractor;
|
||||
_runtimeCorrelator = runtimeCorrelator;
|
||||
_binaryMapper = binaryMapper;
|
||||
_packageMatcher = packageMatcher;
|
||||
_repository = repository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FidelityAnalysisResult> AnalyzeAsync(
|
||||
AnalysisRequest request,
|
||||
FidelityLevel level,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var config = FidelityConfiguration.FromLevel(level);
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
cts.CancelAfter(config.Timeout);
|
||||
|
||||
try
|
||||
{
|
||||
// Level 1: Package matching (always done)
|
||||
var packageResult = await _packageMatcher.MatchAsync(request, cts.Token);
|
||||
|
||||
if (level == FidelityLevel.Quick)
|
||||
{
|
||||
return BuildResult(packageResult, config, stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
// Level 2: Call graph analysis (Standard and Deep)
|
||||
CallGraphResult? callGraphResult = null;
|
||||
if (config.EnableCallGraph)
|
||||
{
|
||||
var languages = config.TargetLanguages ?? request.DetectedLanguages;
|
||||
callGraphResult = await _callGraphExtractor.ExtractAsync(
|
||||
request,
|
||||
languages,
|
||||
config.MaxCallGraphDepth,
|
||||
cts.Token);
|
||||
}
|
||||
|
||||
if (level == FidelityLevel.Standard)
|
||||
{
|
||||
return BuildResult(packageResult, callGraphResult, config, stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
// Level 3: Binary mapping and runtime (Deep only)
|
||||
BinaryMappingResult? binaryResult = null;
|
||||
RuntimeCorrelationResult? runtimeResult = null;
|
||||
|
||||
if (config.EnableBinaryMapping)
|
||||
{
|
||||
binaryResult = await _binaryMapper.MapAsync(request, cts.Token);
|
||||
}
|
||||
|
||||
if (config.EnableRuntimeCorrelation)
|
||||
{
|
||||
runtimeResult = await _runtimeCorrelator.CorrelateAsync(request, cts.Token);
|
||||
}
|
||||
|
||||
return BuildResult(
|
||||
packageResult,
|
||||
callGraphResult,
|
||||
binaryResult,
|
||||
runtimeResult,
|
||||
config,
|
||||
stopwatch.Elapsed);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested && !ct.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Analysis timeout at fidelity {Level} after {Elapsed}",
|
||||
level, stopwatch.Elapsed);
|
||||
|
||||
return BuildTimeoutResult(level, config, stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<FidelityUpgradeResult> UpgradeFidelityAsync(
|
||||
Guid findingId,
|
||||
FidelityLevel targetLevel,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Load existing analysis
|
||||
var existing = await _repository.GetAnalysisAsync(findingId, ct);
|
||||
if (existing is null)
|
||||
{
|
||||
return FidelityUpgradeResult.NotFound(findingId);
|
||||
}
|
||||
|
||||
if (existing.FidelityLevel >= targetLevel)
|
||||
{
|
||||
return FidelityUpgradeResult.AlreadyAtLevel(existing);
|
||||
}
|
||||
|
||||
// Perform incremental upgrade
|
||||
var request = existing.ToAnalysisRequest();
|
||||
var result = await AnalyzeAsync(request, targetLevel, ct);
|
||||
|
||||
// Merge with existing
|
||||
var merged = MergeResults(existing, result);
|
||||
|
||||
// Persist upgraded result
|
||||
await _repository.SaveAnalysisAsync(merged, ct);
|
||||
|
||||
return new FidelityUpgradeResult
|
||||
{
|
||||
Success = true,
|
||||
FindingId = findingId,
|
||||
PreviousLevel = existing.FidelityLevel,
|
||||
NewLevel = targetLevel,
|
||||
ConfidenceImprovement = merged.Confidence - existing.Confidence,
|
||||
NewResult = merged
|
||||
};
|
||||
}
|
||||
|
||||
private FidelityAnalysisResult BuildResult(
|
||||
PackageMatchResult packageResult,
|
||||
FidelityConfiguration config,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var confidence = config.BaseConfidence;
|
||||
|
||||
// Adjust confidence based on match quality
|
||||
if (packageResult.HasExactMatch)
|
||||
confidence += 0.1m;
|
||||
|
||||
return new FidelityAnalysisResult
|
||||
{
|
||||
FidelityLevel = config.Level,
|
||||
Confidence = Math.Min(confidence, 1.0m),
|
||||
IsReachable = null, // Unknown at Quick level
|
||||
PackageMatches = packageResult.Matches,
|
||||
CallGraph = null,
|
||||
BinaryMapping = null,
|
||||
RuntimeCorrelation = null,
|
||||
AnalysisTime = elapsed,
|
||||
TimedOut = false,
|
||||
CanUpgrade = true,
|
||||
UpgradeRecommendation = "Upgrade to Standard for call graph analysis"
|
||||
};
|
||||
}
|
||||
|
||||
private FidelityAnalysisResult BuildResult(
|
||||
PackageMatchResult packageResult,
|
||||
CallGraphResult? callGraphResult,
|
||||
FidelityConfiguration config,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var confidence = config.BaseConfidence;
|
||||
|
||||
// Adjust based on call graph completeness
|
||||
if (callGraphResult?.IsComplete == true)
|
||||
confidence += 0.15m;
|
||||
|
||||
var isReachable = callGraphResult?.HasPathToVulnerable;
|
||||
|
||||
return new FidelityAnalysisResult
|
||||
{
|
||||
FidelityLevel = config.Level,
|
||||
Confidence = Math.Min(confidence, 1.0m),
|
||||
IsReachable = isReachable,
|
||||
PackageMatches = packageResult.Matches,
|
||||
CallGraph = callGraphResult,
|
||||
BinaryMapping = null,
|
||||
RuntimeCorrelation = null,
|
||||
AnalysisTime = elapsed,
|
||||
TimedOut = false,
|
||||
CanUpgrade = true,
|
||||
UpgradeRecommendation = isReachable == true
|
||||
? "Upgrade to Deep for runtime verification"
|
||||
: "Upgrade to Deep for binary mapping confirmation"
|
||||
};
|
||||
}
|
||||
|
||||
private FidelityAnalysisResult BuildResult(
|
||||
PackageMatchResult packageResult,
|
||||
CallGraphResult? callGraphResult,
|
||||
BinaryMappingResult? binaryResult,
|
||||
RuntimeCorrelationResult? runtimeResult,
|
||||
FidelityConfiguration config,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
var confidence = config.BaseConfidence;
|
||||
|
||||
// Adjust based on runtime corroboration
|
||||
if (runtimeResult?.HasCorroboration == true)
|
||||
confidence = 0.95m;
|
||||
else if (binaryResult?.HasMapping == true)
|
||||
confidence += 0.05m;
|
||||
|
||||
var isReachable = DetermineReachability(
|
||||
callGraphResult,
|
||||
binaryResult,
|
||||
runtimeResult);
|
||||
|
||||
return new FidelityAnalysisResult
|
||||
{
|
||||
FidelityLevel = config.Level,
|
||||
Confidence = Math.Min(confidence, 1.0m),
|
||||
IsReachable = isReachable,
|
||||
PackageMatches = packageResult.Matches,
|
||||
CallGraph = callGraphResult,
|
||||
BinaryMapping = binaryResult,
|
||||
RuntimeCorrelation = runtimeResult,
|
||||
AnalysisTime = elapsed,
|
||||
TimedOut = false,
|
||||
CanUpgrade = false,
|
||||
UpgradeRecommendation = null
|
||||
};
|
||||
}
|
||||
|
||||
private static bool? DetermineReachability(
|
||||
CallGraphResult? callGraph,
|
||||
BinaryMappingResult? binary,
|
||||
RuntimeCorrelationResult? runtime)
|
||||
{
|
||||
// Runtime is authoritative
|
||||
if (runtime?.WasExecuted == true)
|
||||
return true;
|
||||
if (runtime?.WasExecuted == false && runtime.ObservationCount > 100)
|
||||
return false;
|
||||
|
||||
// Fall back to call graph
|
||||
if (callGraph?.HasPathToVulnerable == true)
|
||||
return true;
|
||||
if (callGraph?.HasPathToVulnerable == false && callGraph.IsComplete)
|
||||
return false;
|
||||
|
||||
return null; // Unknown
|
||||
}
|
||||
|
||||
private FidelityAnalysisResult BuildTimeoutResult(
|
||||
FidelityLevel attemptedLevel,
|
||||
FidelityConfiguration config,
|
||||
TimeSpan elapsed)
|
||||
{
|
||||
return new FidelityAnalysisResult
|
||||
{
|
||||
FidelityLevel = attemptedLevel,
|
||||
Confidence = 0.3m,
|
||||
IsReachable = null,
|
||||
PackageMatches = [],
|
||||
CallGraph = null,
|
||||
BinaryMapping = null,
|
||||
RuntimeCorrelation = null,
|
||||
AnalysisTime = elapsed,
|
||||
TimedOut = true,
|
||||
CanUpgrade = false,
|
||||
UpgradeRecommendation = "Analysis timed out. Try with smaller scope."
|
||||
};
|
||||
}
|
||||
|
||||
private FidelityAnalysisResult MergeResults(
|
||||
FidelityAnalysisResult existing,
|
||||
FidelityAnalysisResult upgraded)
|
||||
{
|
||||
// Take the upgraded result but preserve any existing data not replaced
|
||||
return new FidelityAnalysisResult
|
||||
{
|
||||
FidelityLevel = upgraded.FidelityLevel,
|
||||
Confidence = upgraded.Confidence,
|
||||
IsReachable = upgraded.IsReachable ?? existing.IsReachable,
|
||||
PackageMatches = upgraded.PackageMatches,
|
||||
CallGraph = upgraded.CallGraph ?? existing.CallGraph,
|
||||
BinaryMapping = upgraded.BinaryMapping ?? existing.BinaryMapping,
|
||||
RuntimeCorrelation = upgraded.RuntimeCorrelation ?? existing.RuntimeCorrelation,
|
||||
AnalysisTime = existing.AnalysisTime + upgraded.AnalysisTime,
|
||||
TimedOut = upgraded.TimedOut,
|
||||
CanUpgrade = upgraded.CanUpgrade,
|
||||
UpgradeRecommendation = upgraded.UpgradeRecommendation
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record FidelityAnalysisResult
|
||||
{
|
||||
public required FidelityLevel FidelityLevel { get; init; }
|
||||
public required decimal Confidence { get; init; }
|
||||
public bool? IsReachable { get; init; }
|
||||
public required IReadOnlyList<PackageMatch> PackageMatches { get; init; }
|
||||
public CallGraphResult? CallGraph { get; init; }
|
||||
public BinaryMappingResult? BinaryMapping { get; init; }
|
||||
public RuntimeCorrelationResult? RuntimeCorrelation { get; init; }
|
||||
public required TimeSpan AnalysisTime { get; init; }
|
||||
public required bool TimedOut { get; init; }
|
||||
public required bool CanUpgrade { get; init; }
|
||||
public string? UpgradeRecommendation { get; init; }
|
||||
|
||||
public AnalysisRequest ToAnalysisRequest()
|
||||
{
|
||||
// Convert back to analysis request for upgrade scenarios
|
||||
return new AnalysisRequest
|
||||
{
|
||||
// Populate from existing result
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record FidelityUpgradeResult
|
||||
{
|
||||
public required bool Success { get; init; }
|
||||
public Guid FindingId { get; init; }
|
||||
public FidelityLevel? PreviousLevel { get; init; }
|
||||
public FidelityLevel? NewLevel { get; init; }
|
||||
public decimal ConfidenceImprovement { get; init; }
|
||||
public FidelityAnalysisResult? NewResult { get; init; }
|
||||
public string? Error { get; init; }
|
||||
|
||||
public static FidelityUpgradeResult NotFound(Guid id) => new()
|
||||
{
|
||||
Success = false,
|
||||
FindingId = id,
|
||||
Error = "Finding not found"
|
||||
};
|
||||
|
||||
public static FidelityUpgradeResult AlreadyAtLevel(FidelityAnalysisResult existing) => new()
|
||||
{
|
||||
Success = true,
|
||||
PreviousLevel = existing.FidelityLevel,
|
||||
NewLevel = existing.FidelityLevel,
|
||||
ConfidenceImprovement = 0,
|
||||
NewResult = existing
|
||||
};
|
||||
}
|
||||
|
||||
// Supporting interfaces and types
|
||||
|
||||
public interface ICallGraphExtractor
|
||||
{
|
||||
Task<CallGraphResult> ExtractAsync(
|
||||
AnalysisRequest request,
|
||||
IReadOnlyList<string> languages,
|
||||
int maxDepth,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public interface IRuntimeCorrelator
|
||||
{
|
||||
Task<RuntimeCorrelationResult> CorrelateAsync(
|
||||
AnalysisRequest request,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public interface IBinaryMapper
|
||||
{
|
||||
Task<BinaryMappingResult> MapAsync(
|
||||
AnalysisRequest request,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public interface IPackageMatcher
|
||||
{
|
||||
Task<PackageMatchResult> MatchAsync(
|
||||
AnalysisRequest request,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public interface IAnalysisRepository
|
||||
{
|
||||
Task<FidelityAnalysisResult?> GetAnalysisAsync(Guid findingId, CancellationToken ct);
|
||||
Task SaveAnalysisAsync(FidelityAnalysisResult result, CancellationToken ct);
|
||||
}
|
||||
|
||||
public sealed record AnalysisRequest
|
||||
{
|
||||
public IReadOnlyList<string> DetectedLanguages { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public sealed record PackageMatchResult
|
||||
{
|
||||
public bool HasExactMatch { get; init; }
|
||||
public IReadOnlyList<PackageMatch> Matches { get; init; } = Array.Empty<PackageMatch>();
|
||||
}
|
||||
|
||||
public sealed record PackageMatch
|
||||
{
|
||||
public required string PackageName { get; init; }
|
||||
public required string Version { get; init; }
|
||||
}
|
||||
|
||||
public sealed record CallGraphResult
|
||||
{
|
||||
public bool IsComplete { get; init; }
|
||||
public bool? HasPathToVulnerable { get; init; }
|
||||
}
|
||||
|
||||
public sealed record BinaryMappingResult
|
||||
{
|
||||
public bool HasMapping { get; init; }
|
||||
}
|
||||
|
||||
public sealed record RuntimeCorrelationResult
|
||||
{
|
||||
public bool? WasExecuted { get; init; }
|
||||
public int ObservationCount { get; init; }
|
||||
public bool HasCorroboration { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
namespace StellaOps.Scanner.Orchestration.Fidelity;
|
||||
|
||||
/// <summary>
|
||||
/// Analysis fidelity level controlling depth vs speed tradeoff.
|
||||
/// </summary>
|
||||
public enum FidelityLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Fast heuristic analysis. Uses package-level matching only.
|
||||
/// ~10x faster than Standard. Lower confidence.
|
||||
/// </summary>
|
||||
Quick,
|
||||
|
||||
/// <summary>
|
||||
/// Standard analysis. Includes call graph for top languages.
|
||||
/// Balanced speed and accuracy.
|
||||
/// </summary>
|
||||
Standard,
|
||||
|
||||
/// <summary>
|
||||
/// Deep analysis. Full call graph, runtime correlation, binary mapping.
|
||||
/// Highest confidence but slowest.
|
||||
/// </summary>
|
||||
Deep
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for each fidelity level.
|
||||
/// </summary>
|
||||
public sealed record FidelityConfiguration
|
||||
{
|
||||
public required FidelityLevel Level { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to perform call graph extraction.
|
||||
/// </summary>
|
||||
public bool EnableCallGraph { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to correlate with runtime evidence.
|
||||
/// </summary>
|
||||
public bool EnableRuntimeCorrelation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to perform binary mapping.
|
||||
/// </summary>
|
||||
public bool EnableBinaryMapping { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum call graph depth.
|
||||
/// </summary>
|
||||
public int MaxCallGraphDepth { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for analysis.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Base confidence for this fidelity level.
|
||||
/// </summary>
|
||||
public decimal BaseConfidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Languages to analyze (null = all).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? TargetLanguages { get; init; }
|
||||
|
||||
public static FidelityConfiguration Quick => new()
|
||||
{
|
||||
Level = FidelityLevel.Quick,
|
||||
EnableCallGraph = false,
|
||||
EnableRuntimeCorrelation = false,
|
||||
EnableBinaryMapping = false,
|
||||
MaxCallGraphDepth = 0,
|
||||
Timeout = TimeSpan.FromSeconds(30),
|
||||
BaseConfidence = 0.5m,
|
||||
TargetLanguages = null
|
||||
};
|
||||
|
||||
public static FidelityConfiguration Standard => new()
|
||||
{
|
||||
Level = FidelityLevel.Standard,
|
||||
EnableCallGraph = true,
|
||||
EnableRuntimeCorrelation = false,
|
||||
EnableBinaryMapping = false,
|
||||
MaxCallGraphDepth = 10,
|
||||
Timeout = TimeSpan.FromMinutes(5),
|
||||
BaseConfidence = 0.75m,
|
||||
TargetLanguages = ["java", "dotnet", "python", "go", "node"]
|
||||
};
|
||||
|
||||
public static FidelityConfiguration Deep => new()
|
||||
{
|
||||
Level = FidelityLevel.Deep,
|
||||
EnableCallGraph = true,
|
||||
EnableRuntimeCorrelation = true,
|
||||
EnableBinaryMapping = true,
|
||||
MaxCallGraphDepth = 50,
|
||||
Timeout = TimeSpan.FromMinutes(30),
|
||||
BaseConfidence = 0.9m,
|
||||
TargetLanguages = null
|
||||
};
|
||||
|
||||
public static FidelityConfiguration FromLevel(FidelityLevel level) => level switch
|
||||
{
|
||||
FidelityLevel.Quick => Quick,
|
||||
FidelityLevel.Standard => Standard,
|
||||
FidelityLevel.Deep => Deep,
|
||||
_ => Standard
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user