feat: Add VEX Status Chip component and integration tests for reachability drift detection
- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips. - Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling. - Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation. - Updated project references to include the new Reachability Drift library.
This commit is contained in:
@@ -0,0 +1,635 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.EntryTrace.FileSystem;
|
||||
|
||||
namespace StellaOps.Scanner.EntryTrace.Baseline;
|
||||
|
||||
/// <summary>
|
||||
/// Context for baseline analysis.
|
||||
/// </summary>
|
||||
public sealed record BaselineAnalysisContext
|
||||
{
|
||||
/// <summary>Scan identifier.</summary>
|
||||
public required string ScanId { get; init; }
|
||||
|
||||
/// <summary>Root path for scanning.</summary>
|
||||
public required string RootPath { get; init; }
|
||||
|
||||
/// <summary>Configuration to use.</summary>
|
||||
public required EntryTraceBaselineConfig Config { get; init; }
|
||||
|
||||
/// <summary>File system abstraction.</summary>
|
||||
public IRootFileSystem? FileSystem { get; init; }
|
||||
|
||||
/// <summary>Known vulnerabilities for reachability analysis.</summary>
|
||||
public IReadOnlyList<string>? KnownVulnerabilities { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for baseline entry point analysis.
|
||||
/// </summary>
|
||||
public interface IBaselineAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs baseline entry point analysis.
|
||||
/// </summary>
|
||||
Task<BaselineReport> AnalyzeAsync(
|
||||
BaselineAnalysisContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Streams detected entry points for large codebases.
|
||||
/// </summary>
|
||||
IAsyncEnumerable<DetectedEntryPoint> StreamEntryPointsAsync(
|
||||
BaselineAnalysisContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pattern-based baseline analyzer for entry point detection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implements SCANNER-ENTRYTRACE-18-508: EntryTrace baseline analysis.
|
||||
/// </remarks>
|
||||
public sealed class BaselineAnalyzer : IBaselineAnalyzer
|
||||
{
|
||||
private readonly ILogger<BaselineAnalyzer> _logger;
|
||||
private readonly Dictionary<string, Regex> _compiledPatterns = new();
|
||||
|
||||
public BaselineAnalyzer(ILogger<BaselineAnalyzer> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BaselineReport> AnalyzeAsync(
|
||||
BaselineAnalysisContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var entryPoints = new List<DetectedEntryPoint>();
|
||||
var frameworksDetected = new HashSet<string>();
|
||||
var filesAnalyzed = 0;
|
||||
var filesSkipped = 0;
|
||||
|
||||
_logger.LogInformation("Starting baseline analysis for scan {ScanId}", context.ScanId);
|
||||
|
||||
await foreach (var entryPoint in StreamEntryPointsAsync(context, cancellationToken))
|
||||
{
|
||||
entryPoints.Add(entryPoint);
|
||||
if (entryPoint.Framework is not null)
|
||||
{
|
||||
frameworksDetected.Add(entryPoint.Framework);
|
||||
}
|
||||
}
|
||||
|
||||
// Count files (simplified - would need proper tracking in production)
|
||||
filesAnalyzed = await CountFilesAsync(context, cancellationToken);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
var statistics = ComputeStatistics(entryPoints, filesAnalyzed, filesSkipped);
|
||||
var digest = BaselineReport.ComputeDigest(entryPoints);
|
||||
|
||||
var report = new BaselineReport
|
||||
{
|
||||
ReportId = Guid.NewGuid(),
|
||||
ScanId = context.ScanId,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
ConfigUsed = context.Config.ConfigId,
|
||||
EntryPoints = entryPoints.ToImmutableArray(),
|
||||
Statistics = statistics,
|
||||
FrameworksDetected = frameworksDetected.OrderBy(f => f).ToImmutableArray(),
|
||||
AnalysisDurationMs = stopwatch.ElapsedMilliseconds,
|
||||
Digest = digest
|
||||
};
|
||||
|
||||
_logger.LogInformation(
|
||||
"Baseline analysis complete: {EntryPointCount} entry points in {Duration}ms",
|
||||
entryPoints.Count, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<DetectedEntryPoint> StreamEntryPointsAsync(
|
||||
BaselineAnalysisContext context,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var config = context.Config;
|
||||
var fileExtensions = GetFileExtensions(config.Language);
|
||||
var excludePatterns = BuildExcludePatterns(config.Exclusions);
|
||||
|
||||
await foreach (var filePath in EnumerateFilesAsync(context.RootPath, fileExtensions, cancellationToken))
|
||||
{
|
||||
if (ShouldExclude(filePath, excludePatterns, config.Exclusions))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string content;
|
||||
try
|
||||
{
|
||||
content = await File.ReadAllTextAsync(filePath, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to read file {FilePath}", filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = Path.GetRelativePath(context.RootPath, filePath);
|
||||
var lines = content.Split('\n');
|
||||
var detectedFramework = DetectFramework(content, config.FrameworkConfigs);
|
||||
|
||||
foreach (var pattern in config.EntryPointPatterns)
|
||||
{
|
||||
// Skip patterns not for this framework
|
||||
if (pattern.Framework is not null && detectedFramework is not null &&
|
||||
!pattern.Framework.Equals(detectedFramework, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matches = FindMatches(content, lines, pattern, relativePath);
|
||||
foreach (var match in matches)
|
||||
{
|
||||
if (match.Confidence >= config.Heuristics.ConfidenceThreshold)
|
||||
{
|
||||
var entryPoint = CreateEntryPoint(match, pattern, detectedFramework);
|
||||
yield return entryPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PatternMatch> FindMatches(
|
||||
string content,
|
||||
string[] lines,
|
||||
EntryPointPattern pattern,
|
||||
string filePath)
|
||||
{
|
||||
var regex = GetCompiledPattern(pattern);
|
||||
if (regex is null)
|
||||
yield break;
|
||||
|
||||
var matches = regex.Matches(content);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
var (line, column) = GetLineAndColumn(content, match.Index);
|
||||
var functionName = ExtractFunctionName(lines, line);
|
||||
|
||||
var confidence = CalculateConfidence(pattern, match, lines, line);
|
||||
|
||||
yield return new PatternMatch
|
||||
{
|
||||
FilePath = filePath,
|
||||
Line = line,
|
||||
Column = column,
|
||||
MatchedText = match.Value,
|
||||
FunctionName = functionName,
|
||||
Pattern = pattern,
|
||||
Confidence = confidence,
|
||||
Groups = match.Groups.Cast<Group>()
|
||||
.Where(g => g.Success && !string.IsNullOrEmpty(g.Name) && !int.TryParse(g.Name, out _))
|
||||
.ToImmutableDictionary(g => g.Name, g => g.Value)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private Regex? GetCompiledPattern(EntryPointPattern pattern)
|
||||
{
|
||||
if (_compiledPatterns.TryGetValue(pattern.PatternId, out var cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var regex = new Regex(
|
||||
pattern.Pattern,
|
||||
RegexOptions.Compiled | RegexOptions.Multiline,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
_compiledPatterns[pattern.PatternId] = regex;
|
||||
return regex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to compile pattern {PatternId}: {Pattern}",
|
||||
pattern.PatternId, pattern.Pattern);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string? DetectFramework(string content, ImmutableArray<FrameworkConfig> frameworks)
|
||||
{
|
||||
foreach (var framework in frameworks)
|
||||
{
|
||||
foreach (var detection in framework.DetectionPatterns)
|
||||
{
|
||||
if (content.Contains(detection, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return framework.FrameworkId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static (int line, int column) GetLineAndColumn(string content, int index)
|
||||
{
|
||||
var line = 1;
|
||||
var lastNewline = -1;
|
||||
|
||||
for (var i = 0; i < index && i < content.Length; i++)
|
||||
{
|
||||
if (content[i] == '\n')
|
||||
{
|
||||
line++;
|
||||
lastNewline = i;
|
||||
}
|
||||
}
|
||||
|
||||
var column = index - lastNewline;
|
||||
return (line, column);
|
||||
}
|
||||
|
||||
private static string? ExtractFunctionName(string[] lines, int lineNumber)
|
||||
{
|
||||
if (lineNumber < 1 || lineNumber > lines.Length)
|
||||
return null;
|
||||
|
||||
var line = lines[lineNumber - 1];
|
||||
|
||||
// Try common function/method patterns
|
||||
var patterns = new[]
|
||||
{
|
||||
@"(?:def|function|func)\s+(\w+)", // Python, JS, Go
|
||||
@"(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(", // Java/C#
|
||||
@"(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\()", // JS arrow/named
|
||||
};
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
var match = Regex.Match(line, pattern);
|
||||
if (match.Success && match.Groups.Count > 1)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private double CalculateConfidence(
|
||||
EntryPointPattern pattern,
|
||||
Match match,
|
||||
string[] lines,
|
||||
int lineNumber)
|
||||
{
|
||||
var baseConfidence = pattern.Confidence;
|
||||
|
||||
// Boost for annotation patterns (highest reliability)
|
||||
if (pattern.Type == PatternType.Annotation || pattern.Type == PatternType.Decorator)
|
||||
{
|
||||
baseConfidence = Math.Min(1.0, baseConfidence * 1.1);
|
||||
}
|
||||
|
||||
// Check surrounding context for additional confidence
|
||||
if (lineNumber > 0 && lineNumber <= lines.Length)
|
||||
{
|
||||
var line = lines[lineNumber - 1];
|
||||
|
||||
// Boost if line contains routing keywords
|
||||
if (Regex.IsMatch(line, @"\b(route|path|endpoint|api|handler)\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
baseConfidence = Math.Min(1.0, baseConfidence + 0.05);
|
||||
}
|
||||
|
||||
// Reduce for test files (if not already excluded)
|
||||
if (Regex.IsMatch(line, @"\b(test|spec|mock)\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
baseConfidence *= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Round(baseConfidence, 3);
|
||||
}
|
||||
|
||||
private DetectedEntryPoint CreateEntryPoint(
|
||||
PatternMatch match,
|
||||
EntryPointPattern pattern,
|
||||
string? framework)
|
||||
{
|
||||
var entryId = DetectedEntryPoint.GenerateEntryId(
|
||||
match.FilePath,
|
||||
match.FunctionName ?? "anonymous",
|
||||
match.Line,
|
||||
pattern.EntryType);
|
||||
|
||||
var httpMetadata = ExtractHttpMetadata(match, pattern);
|
||||
var parameters = ExtractParameters(match, pattern);
|
||||
|
||||
return new DetectedEntryPoint
|
||||
{
|
||||
EntryId = entryId,
|
||||
Type = pattern.EntryType,
|
||||
Name = match.FunctionName ?? "anonymous",
|
||||
Location = new CodeLocation
|
||||
{
|
||||
FilePath = match.FilePath,
|
||||
LineStart = match.Line,
|
||||
LineEnd = match.Line,
|
||||
ColumnStart = match.Column,
|
||||
ColumnEnd = match.Column + match.MatchedText.Length,
|
||||
FunctionName = match.FunctionName
|
||||
},
|
||||
Confidence = match.Confidence,
|
||||
Framework = framework ?? pattern.Framework,
|
||||
HttpMetadata = httpMetadata,
|
||||
Parameters = parameters,
|
||||
DetectionMethod = pattern.PatternId
|
||||
};
|
||||
}
|
||||
|
||||
private HttpMetadata? ExtractHttpMetadata(PatternMatch match, EntryPointPattern pattern)
|
||||
{
|
||||
if (pattern.EntryType != EntryPointType.HttpEndpoint)
|
||||
return null;
|
||||
|
||||
// Try to extract HTTP method and path from match groups
|
||||
var method = HttpMethod.GET;
|
||||
var path = "/";
|
||||
|
||||
if (match.Groups.TryGetValue("method", out var methodStr))
|
||||
{
|
||||
method = ParseHttpMethod(methodStr);
|
||||
}
|
||||
else if (pattern.PatternId.Contains("get", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
method = HttpMethod.GET;
|
||||
}
|
||||
else if (pattern.PatternId.Contains("post", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
method = HttpMethod.POST;
|
||||
}
|
||||
else if (pattern.PatternId.Contains("put", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
method = HttpMethod.PUT;
|
||||
}
|
||||
else if (pattern.PatternId.Contains("delete", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
method = HttpMethod.DELETE;
|
||||
}
|
||||
|
||||
if (match.Groups.TryGetValue("path", out var pathStr))
|
||||
{
|
||||
path = pathStr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to extract path from matched text
|
||||
var pathMatch = Regex.Match(match.MatchedText, @"['""]([^'""]+)['""]");
|
||||
if (pathMatch.Success)
|
||||
{
|
||||
path = pathMatch.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract path parameters
|
||||
var pathParams = Regex.Matches(path, @":(\w+)|{(\w+)}")
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Groups[1].Success ? m.Groups[1].Value : m.Groups[2].Value)
|
||||
.ToImmutableArray();
|
||||
|
||||
return new HttpMetadata
|
||||
{
|
||||
Method = method,
|
||||
Path = path,
|
||||
PathParameters = pathParams
|
||||
};
|
||||
}
|
||||
|
||||
private static HttpMethod ParseHttpMethod(string method)
|
||||
{
|
||||
return method.ToUpperInvariant() switch
|
||||
{
|
||||
"GET" => HttpMethod.GET,
|
||||
"POST" => HttpMethod.POST,
|
||||
"PUT" => HttpMethod.PUT,
|
||||
"PATCH" => HttpMethod.PATCH,
|
||||
"DELETE" => HttpMethod.DELETE,
|
||||
"HEAD" => HttpMethod.HEAD,
|
||||
"OPTIONS" => HttpMethod.OPTIONS,
|
||||
_ => HttpMethod.GET
|
||||
};
|
||||
}
|
||||
|
||||
private static ImmutableArray<ParameterInfo> ExtractParameters(PatternMatch match, EntryPointPattern pattern)
|
||||
{
|
||||
var parameters = new List<ParameterInfo>();
|
||||
|
||||
// Extract path parameters from HTTP metadata
|
||||
if (pattern.EntryType == EntryPointType.HttpEndpoint)
|
||||
{
|
||||
var pathMatch = Regex.Match(match.MatchedText, @"['""]([^'""]+)['""]");
|
||||
if (pathMatch.Success)
|
||||
{
|
||||
var path = pathMatch.Groups[1].Value;
|
||||
var pathParams = Regex.Matches(path, @":(\w+)|{(\w+)}");
|
||||
|
||||
foreach (Match pm in pathParams)
|
||||
{
|
||||
var name = pm.Groups[1].Success ? pm.Groups[1].Value : pm.Groups[2].Value;
|
||||
parameters.Add(new ParameterInfo
|
||||
{
|
||||
Name = name,
|
||||
Source = ParameterSource.Path,
|
||||
Required = true,
|
||||
Tainted = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameters.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetFileExtensions(EntryTraceLanguage language)
|
||||
{
|
||||
return language switch
|
||||
{
|
||||
EntryTraceLanguage.Java => new[] { ".java" },
|
||||
EntryTraceLanguage.Python => new[] { ".py" },
|
||||
EntryTraceLanguage.JavaScript => new[] { ".js", ".mjs", ".cjs" },
|
||||
EntryTraceLanguage.TypeScript => new[] { ".ts", ".tsx", ".mts", ".cts" },
|
||||
EntryTraceLanguage.Go => new[] { ".go" },
|
||||
EntryTraceLanguage.Ruby => new[] { ".rb" },
|
||||
EntryTraceLanguage.Php => new[] { ".php" },
|
||||
EntryTraceLanguage.CSharp => new[] { ".cs" },
|
||||
EntryTraceLanguage.Rust => new[] { ".rs" },
|
||||
_ => Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Regex> BuildExcludePatterns(ExclusionConfig exclusions)
|
||||
{
|
||||
var patterns = new List<Regex>();
|
||||
|
||||
foreach (var glob in exclusions.ExcludePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Convert glob to regex
|
||||
var pattern = "^" + Regex.Escape(glob)
|
||||
.Replace(@"\*\*", ".*")
|
||||
.Replace(@"\*", "[^/\\\\]*")
|
||||
.Replace(@"\?", ".") + "$";
|
||||
|
||||
patterns.Add(new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip invalid patterns
|
||||
}
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
private static bool ShouldExclude(string filePath, IReadOnlyList<Regex> excludePatterns, ExclusionConfig config)
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
var normalizedPath = filePath.Replace('\\', '/');
|
||||
|
||||
// Check test file exclusion
|
||||
if (config.ExcludeTestFiles)
|
||||
{
|
||||
if (Regex.IsMatch(fileName, @"[._-]?(test|spec|tests|specs)[._-]?", RegexOptions.IgnoreCase) ||
|
||||
normalizedPath.Contains("/test/", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedPath.Contains("/tests/", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedPath.Contains("/__tests__/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check generated file exclusion
|
||||
if (config.ExcludeGenerated)
|
||||
{
|
||||
if (normalizedPath.Contains("/generated/", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedPath.Contains("/gen/", StringComparison.OrdinalIgnoreCase) ||
|
||||
fileName.EndsWith(".generated.cs", StringComparison.OrdinalIgnoreCase) ||
|
||||
fileName.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check glob patterns
|
||||
foreach (var pattern in excludePatterns)
|
||||
{
|
||||
if (pattern.IsMatch(normalizedPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async IAsyncEnumerable<string> EnumerateFilesAsync(
|
||||
string rootPath,
|
||||
IEnumerable<string> extensions,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var extensionSet = extensions.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
IEnumerable<string> files;
|
||||
try
|
||||
{
|
||||
files = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ext = Path.GetExtension(file);
|
||||
if (extensionSet.Contains(ext))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static async Task<int> CountFilesAsync(BaselineAnalysisContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var extensions = GetFileExtensions(context.Config.Language);
|
||||
var count = 0;
|
||||
|
||||
await foreach (var _ in EnumerateFilesAsync(context.RootPath, extensions, cancellationToken))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static BaselineStatistics ComputeStatistics(
|
||||
List<DetectedEntryPoint> entryPoints,
|
||||
int filesAnalyzed,
|
||||
int filesSkipped)
|
||||
{
|
||||
var byType = entryPoints
|
||||
.GroupBy(e => e.Type)
|
||||
.ToImmutableDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
var byFramework = entryPoints
|
||||
.Where(e => e.Framework is not null)
|
||||
.GroupBy(e => e.Framework!)
|
||||
.ToImmutableDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
var highConfidence = entryPoints.Count(e => e.Confidence >= 0.8);
|
||||
var mediumConfidence = entryPoints.Count(e => e.Confidence >= 0.5 && e.Confidence < 0.8);
|
||||
var lowConfidence = entryPoints.Count(e => e.Confidence < 0.5);
|
||||
|
||||
var reachableVulns = entryPoints
|
||||
.SelectMany(e => e.ReachableVulnerabilities)
|
||||
.Distinct()
|
||||
.Count();
|
||||
|
||||
return new BaselineStatistics
|
||||
{
|
||||
TotalEntryPoints = entryPoints.Count,
|
||||
ByType = byType,
|
||||
ByFramework = byFramework,
|
||||
HighConfidenceCount = highConfidence,
|
||||
MediumConfidenceCount = mediumConfidence,
|
||||
LowConfidenceCount = lowConfidence,
|
||||
FilesAnalyzed = filesAnalyzed,
|
||||
FilesSkipped = filesSkipped,
|
||||
ReachableVulnerabilities = reachableVulns
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record PatternMatch
|
||||
{
|
||||
public required string FilePath { get; init; }
|
||||
public required int Line { get; init; }
|
||||
public required int Column { get; init; }
|
||||
public required string MatchedText { get; init; }
|
||||
public string? FunctionName { get; init; }
|
||||
public required EntryPointPattern Pattern { get; init; }
|
||||
public required double Confidence { get; init; }
|
||||
public ImmutableDictionary<string, string> Groups { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.EntryTrace.Baseline;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for entry trace baseline analysis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implements SCANNER-ENTRYTRACE-18-508: EntryTrace baseline schema per
|
||||
/// docs/schemas/scanner-entrytrace-baseline.schema.json
|
||||
/// </remarks>
|
||||
public sealed record EntryTraceBaselineConfig
|
||||
{
|
||||
/// <summary>Unique configuration identifier.</summary>
|
||||
public required string ConfigId { get; init; }
|
||||
|
||||
/// <summary>Target language for this configuration.</summary>
|
||||
public required EntryTraceLanguage Language { get; init; }
|
||||
|
||||
/// <summary>Configuration version.</summary>
|
||||
public string? Version { get; init; }
|
||||
|
||||
/// <summary>Entry point detection patterns.</summary>
|
||||
public ImmutableArray<EntryPointPattern> EntryPointPatterns { get; init; } = ImmutableArray<EntryPointPattern>.Empty;
|
||||
|
||||
/// <summary>Framework-specific configurations.</summary>
|
||||
public ImmutableArray<FrameworkConfig> FrameworkConfigs { get; init; } = ImmutableArray<FrameworkConfig>.Empty;
|
||||
|
||||
/// <summary>Heuristics configuration.</summary>
|
||||
public HeuristicsConfig Heuristics { get; init; } = HeuristicsConfig.Default;
|
||||
|
||||
/// <summary>Exclusion rules.</summary>
|
||||
public ExclusionConfig Exclusions { get; init; } = ExclusionConfig.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported languages for entry trace analysis.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum EntryTraceLanguage
|
||||
{
|
||||
Java,
|
||||
Python,
|
||||
JavaScript,
|
||||
TypeScript,
|
||||
Go,
|
||||
Ruby,
|
||||
Php,
|
||||
CSharp,
|
||||
Rust
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types of entry points that can be detected.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum EntryPointType
|
||||
{
|
||||
/// <summary>HTTP/REST endpoint.</summary>
|
||||
HttpEndpoint,
|
||||
|
||||
/// <summary>gRPC method.</summary>
|
||||
GrpcMethod,
|
||||
|
||||
/// <summary>CLI command handler.</summary>
|
||||
CliCommand,
|
||||
|
||||
/// <summary>Event handler (Kafka, RabbitMQ, etc.).</summary>
|
||||
EventHandler,
|
||||
|
||||
/// <summary>Scheduled job (cron, timer).</summary>
|
||||
ScheduledJob,
|
||||
|
||||
/// <summary>Message queue consumer.</summary>
|
||||
MessageConsumer,
|
||||
|
||||
/// <summary>Test method (for test coverage).</summary>
|
||||
TestMethod
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pattern types for detecting entry points.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum PatternType
|
||||
{
|
||||
/// <summary>Annotation/attribute match (e.g., @GetMapping).</summary>
|
||||
Annotation,
|
||||
|
||||
/// <summary>Decorator match (e.g., @app.route).</summary>
|
||||
Decorator,
|
||||
|
||||
/// <summary>Function name pattern.</summary>
|
||||
FunctionName,
|
||||
|
||||
/// <summary>Class name pattern.</summary>
|
||||
ClassName,
|
||||
|
||||
/// <summary>File path pattern.</summary>
|
||||
FilePattern,
|
||||
|
||||
/// <summary>Import statement pattern.</summary>
|
||||
ImportPattern,
|
||||
|
||||
/// <summary>AST pattern for complex matching.</summary>
|
||||
AstPattern
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pattern for detecting entry points.
|
||||
/// </summary>
|
||||
public sealed record EntryPointPattern
|
||||
{
|
||||
/// <summary>Unique pattern identifier.</summary>
|
||||
public required string PatternId { get; init; }
|
||||
|
||||
/// <summary>Type of pattern matching to use.</summary>
|
||||
public required PatternType Type { get; init; }
|
||||
|
||||
/// <summary>Regex or AST pattern string.</summary>
|
||||
public required string Pattern { get; init; }
|
||||
|
||||
/// <summary>Confidence level for matches (0.0-1.0).</summary>
|
||||
public double Confidence { get; init; } = 0.7;
|
||||
|
||||
/// <summary>Type of entry point this pattern detects.</summary>
|
||||
public EntryPointType EntryType { get; init; } = EntryPointType.HttpEndpoint;
|
||||
|
||||
/// <summary>Associated framework name.</summary>
|
||||
public string? Framework { get; init; }
|
||||
|
||||
/// <summary>Rules for extracting metadata from matches.</summary>
|
||||
public MetadataExtractionRules? MetadataExtraction { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rules for extracting metadata from entry point matches.
|
||||
/// </summary>
|
||||
public sealed record MetadataExtractionRules
|
||||
{
|
||||
/// <summary>Expression to extract HTTP method.</summary>
|
||||
public string? HttpMethod { get; init; }
|
||||
|
||||
/// <summary>Expression to extract route path.</summary>
|
||||
public string? RoutePath { get; init; }
|
||||
|
||||
/// <summary>Expression to extract parameters.</summary>
|
||||
public string? Parameters { get; init; }
|
||||
|
||||
/// <summary>Expression to detect auth requirements.</summary>
|
||||
public string? AuthRequired { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Framework-specific configuration.
|
||||
/// </summary>
|
||||
public sealed record FrameworkConfig
|
||||
{
|
||||
/// <summary>Unique framework identifier.</summary>
|
||||
public required string FrameworkId { get; init; }
|
||||
|
||||
/// <summary>Display name.</summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>Supported version range (semver).</summary>
|
||||
public string? VersionRange { get; init; }
|
||||
|
||||
/// <summary>Patterns to detect framework usage.</summary>
|
||||
public ImmutableArray<string> DetectionPatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Entry point pattern IDs applicable to this framework.</summary>
|
||||
public ImmutableArray<string> EntryPatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Glob patterns for router/route files.</summary>
|
||||
public ImmutableArray<string> RouterFilePatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Patterns to identify controller classes.</summary>
|
||||
public ImmutableArray<string> ControllerPatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heuristics configuration for entry point detection.
|
||||
/// </summary>
|
||||
public sealed record HeuristicsConfig
|
||||
{
|
||||
/// <summary>Enable static code analysis.</summary>
|
||||
public bool EnableStaticAnalysis { get; init; } = true;
|
||||
|
||||
/// <summary>Use runtime hints if available.</summary>
|
||||
public bool EnableDynamicHints { get; init; } = false;
|
||||
|
||||
/// <summary>Minimum confidence to report entry point.</summary>
|
||||
public double ConfidenceThreshold { get; init; } = 0.7;
|
||||
|
||||
/// <summary>Maximum call graph depth to analyze.</summary>
|
||||
public int MaxDepth { get; init; } = 10;
|
||||
|
||||
/// <summary>Analysis timeout per file in seconds.</summary>
|
||||
public int TimeoutSeconds { get; init; } = 300;
|
||||
|
||||
/// <summary>Scoring weights for confidence calculation.</summary>
|
||||
public ScoringWeights Weights { get; init; } = ScoringWeights.Default;
|
||||
|
||||
public static HeuristicsConfig Default => new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Weights for confidence scoring.
|
||||
/// </summary>
|
||||
public sealed record ScoringWeights
|
||||
{
|
||||
/// <summary>Weight for annotation/decorator matches.</summary>
|
||||
public double AnnotationMatch { get; init; } = 0.9;
|
||||
|
||||
/// <summary>Weight for naming convention matches.</summary>
|
||||
public double NamingConvention { get; init; } = 0.6;
|
||||
|
||||
/// <summary>Weight for file location patterns.</summary>
|
||||
public double FileLocation { get; init; } = 0.5;
|
||||
|
||||
/// <summary>Weight for import analysis.</summary>
|
||||
public double ImportAnalysis { get; init; } = 0.7;
|
||||
|
||||
/// <summary>Weight for call graph centrality.</summary>
|
||||
public double CallGraphCentrality { get; init; } = 0.4;
|
||||
|
||||
public static ScoringWeights Default => new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exclusion rules for analysis.
|
||||
/// </summary>
|
||||
public sealed record ExclusionConfig
|
||||
{
|
||||
/// <summary>Glob patterns for paths to exclude.</summary>
|
||||
public ImmutableArray<string> ExcludePaths { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Package names to exclude.</summary>
|
||||
public ImmutableArray<string> ExcludePackages { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Exclude test files from analysis.</summary>
|
||||
public bool ExcludeTestFiles { get; init; } = true;
|
||||
|
||||
/// <summary>Exclude generated files from analysis.</summary>
|
||||
public bool ExcludeGenerated { get; init; } = true;
|
||||
|
||||
public static ExclusionConfig Default => new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Source code location.
|
||||
/// </summary>
|
||||
public sealed record CodeLocation
|
||||
{
|
||||
/// <summary>File path relative to scan root.</summary>
|
||||
public required string FilePath { get; init; }
|
||||
|
||||
/// <summary>Starting line number (1-indexed).</summary>
|
||||
public int LineStart { get; init; }
|
||||
|
||||
/// <summary>Ending line number.</summary>
|
||||
public int LineEnd { get; init; }
|
||||
|
||||
/// <summary>Starting column.</summary>
|
||||
public int ColumnStart { get; init; }
|
||||
|
||||
/// <summary>Ending column.</summary>
|
||||
public int ColumnEnd { get; init; }
|
||||
|
||||
/// <summary>Containing function name.</summary>
|
||||
public string? FunctionName { get; init; }
|
||||
|
||||
/// <summary>Containing class name.</summary>
|
||||
public string? ClassName { get; init; }
|
||||
|
||||
/// <summary>Package/namespace name.</summary>
|
||||
public string? PackageName { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP endpoint metadata.
|
||||
/// </summary>
|
||||
public sealed record HttpMetadata
|
||||
{
|
||||
/// <summary>HTTP method.</summary>
|
||||
public HttpMethod Method { get; init; } = HttpMethod.GET;
|
||||
|
||||
/// <summary>Route path.</summary>
|
||||
public required string Path { get; init; }
|
||||
|
||||
/// <summary>Path parameters.</summary>
|
||||
public ImmutableArray<string> PathParameters { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Query parameters.</summary>
|
||||
public ImmutableArray<string> QueryParameters { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Consumed content types.</summary>
|
||||
public ImmutableArray<string> Consumes { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Produced content types.</summary>
|
||||
public ImmutableArray<string> Produces { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Whether authentication is required.</summary>
|
||||
public bool AuthRequired { get; init; }
|
||||
|
||||
/// <summary>Required auth scopes.</summary>
|
||||
public ImmutableArray<string> AuthScopes { get; init; } = ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP methods.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum HttpMethod
|
||||
{
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
HEAD,
|
||||
OPTIONS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameter source types.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ParameterSource
|
||||
{
|
||||
Path,
|
||||
Query,
|
||||
Header,
|
||||
Body,
|
||||
Form,
|
||||
Cookie
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point parameter information.
|
||||
/// </summary>
|
||||
public sealed record ParameterInfo
|
||||
{
|
||||
/// <summary>Parameter name.</summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>Parameter type.</summary>
|
||||
public string? Type { get; init; }
|
||||
|
||||
/// <summary>Source of the parameter value.</summary>
|
||||
public ParameterSource Source { get; init; } = ParameterSource.Query;
|
||||
|
||||
/// <summary>Whether the parameter is required.</summary>
|
||||
public bool Required { get; init; }
|
||||
|
||||
/// <summary>Whether this is a potential taint source.</summary>
|
||||
public bool Tainted { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call type in call graph.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CallType
|
||||
{
|
||||
Direct,
|
||||
Virtual,
|
||||
Interface,
|
||||
Reflection,
|
||||
Lambda
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual call site in a call path.
|
||||
/// </summary>
|
||||
public sealed record CallSite
|
||||
{
|
||||
/// <summary>Caller function/method.</summary>
|
||||
public required string Caller { get; init; }
|
||||
|
||||
/// <summary>Callee function/method.</summary>
|
||||
public required string Callee { get; init; }
|
||||
|
||||
/// <summary>Source location.</summary>
|
||||
public CodeLocation? Location { get; init; }
|
||||
|
||||
/// <summary>Type of call.</summary>
|
||||
public CallType CallType { get; init; } = CallType.Direct;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call path from entry point to vulnerability.
|
||||
/// </summary>
|
||||
public sealed record CallPath
|
||||
{
|
||||
/// <summary>Target CVE or vulnerability identifier.</summary>
|
||||
public required string TargetVulnerability { get; init; }
|
||||
|
||||
/// <summary>Number of calls in the path.</summary>
|
||||
public int PathLength { get; init; }
|
||||
|
||||
/// <summary>Call sites along the path.</summary>
|
||||
public ImmutableArray<CallSite> Calls { get; init; } = ImmutableArray<CallSite>.Empty;
|
||||
|
||||
/// <summary>Confidence in the path (0.0-1.0).</summary>
|
||||
public double Confidence { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detected entry point.
|
||||
/// </summary>
|
||||
public sealed record DetectedEntryPoint
|
||||
{
|
||||
/// <summary>Unique entry point identifier (deterministic).</summary>
|
||||
public required string EntryId { get; init; }
|
||||
|
||||
/// <summary>Type of entry point.</summary>
|
||||
public required EntryPointType Type { get; init; }
|
||||
|
||||
/// <summary>Entry point name (function/method name).</summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>Source code location.</summary>
|
||||
public required CodeLocation Location { get; init; }
|
||||
|
||||
/// <summary>Detection confidence (0.0-1.0).</summary>
|
||||
public double Confidence { get; init; }
|
||||
|
||||
/// <summary>Detected framework.</summary>
|
||||
public string? Framework { get; init; }
|
||||
|
||||
/// <summary>HTTP-specific metadata (if applicable).</summary>
|
||||
public HttpMetadata? HttpMetadata { get; init; }
|
||||
|
||||
/// <summary>Parameters of the entry point.</summary>
|
||||
public ImmutableArray<ParameterInfo> Parameters { get; init; } = ImmutableArray<ParameterInfo>.Empty;
|
||||
|
||||
/// <summary>CVE IDs reachable from this entry point.</summary>
|
||||
public ImmutableArray<string> ReachableVulnerabilities { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Call paths to vulnerabilities.</summary>
|
||||
public ImmutableArray<CallPath> CallPaths { get; init; } = ImmutableArray<CallPath>.Empty;
|
||||
|
||||
/// <summary>Pattern ID that detected this entry point.</summary>
|
||||
public string? DetectionMethod { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a deterministic entry ID.
|
||||
/// </summary>
|
||||
public static string GenerateEntryId(string filePath, string name, int line, EntryPointType type)
|
||||
{
|
||||
var input = $"{filePath}|{name}|{line}|{type}";
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return $"ep:{Convert.ToHexString(hash).ToLowerInvariant()[..16]}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline analysis statistics.
|
||||
/// </summary>
|
||||
public sealed record BaselineStatistics
|
||||
{
|
||||
/// <summary>Total number of entry points detected.</summary>
|
||||
public int TotalEntryPoints { get; init; }
|
||||
|
||||
/// <summary>Entry points by type.</summary>
|
||||
public ImmutableDictionary<EntryPointType, int> ByType { get; init; } =
|
||||
ImmutableDictionary<EntryPointType, int>.Empty;
|
||||
|
||||
/// <summary>Entry points by framework.</summary>
|
||||
public ImmutableDictionary<string, int> ByFramework { get; init; } =
|
||||
ImmutableDictionary<string, int>.Empty;
|
||||
|
||||
/// <summary>Entry points by confidence level.</summary>
|
||||
public int HighConfidenceCount { get; init; }
|
||||
public int MediumConfidenceCount { get; init; }
|
||||
public int LowConfidenceCount { get; init; }
|
||||
|
||||
/// <summary>Number of files analyzed.</summary>
|
||||
public int FilesAnalyzed { get; init; }
|
||||
|
||||
/// <summary>Number of files skipped.</summary>
|
||||
public int FilesSkipped { get; init; }
|
||||
|
||||
/// <summary>Number of reachable vulnerabilities.</summary>
|
||||
public int ReachableVulnerabilities { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry trace baseline analysis report.
|
||||
/// </summary>
|
||||
public sealed record BaselineReport
|
||||
{
|
||||
/// <summary>Unique report identifier.</summary>
|
||||
public required Guid ReportId { get; init; }
|
||||
|
||||
/// <summary>Scan identifier.</summary>
|
||||
public required string ScanId { get; init; }
|
||||
|
||||
/// <summary>Report generation timestamp (UTC ISO-8601).</summary>
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
/// <summary>Configuration ID used for analysis.</summary>
|
||||
public string? ConfigUsed { get; init; }
|
||||
|
||||
/// <summary>Detected entry points.</summary>
|
||||
public ImmutableArray<DetectedEntryPoint> EntryPoints { get; init; } =
|
||||
ImmutableArray<DetectedEntryPoint>.Empty;
|
||||
|
||||
/// <summary>Analysis statistics.</summary>
|
||||
public BaselineStatistics Statistics { get; init; } = new();
|
||||
|
||||
/// <summary>Detected frameworks.</summary>
|
||||
public ImmutableArray<string> FrameworksDetected { get; init; } =
|
||||
ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Analysis duration in milliseconds.</summary>
|
||||
public long AnalysisDurationMs { get; init; }
|
||||
|
||||
/// <summary>Report digest (sha256:...).</summary>
|
||||
public required string Digest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Computes the digest for this report.
|
||||
/// </summary>
|
||||
public static string ComputeDigest(IEnumerable<DetectedEntryPoint> entryPoints)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ep in entryPoints.OrderBy(e => e.EntryId))
|
||||
{
|
||||
sb.Append(ep.EntryId);
|
||||
sb.Append('|');
|
||||
}
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sb.ToString()));
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace StellaOps.Scanner.EntryTrace.Baseline;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering baseline analysis services.
|
||||
/// </summary>
|
||||
public static class BaselineServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds baseline entry point analysis services to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEntryTraceBaseline(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<IBaselineAnalyzer, BaselineAnalyzer>();
|
||||
services.TryAddSingleton<IBaselineConfigProvider, DefaultBaselineConfigProvider>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds baseline entry point analysis with custom configurations.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEntryTraceBaseline(
|
||||
this IServiceCollection services,
|
||||
Action<BaselineAnalyzerOptions> configure)
|
||||
{
|
||||
services.Configure(configure);
|
||||
services.TryAddSingleton<IBaselineAnalyzer, BaselineAnalyzer>();
|
||||
services.TryAddSingleton<IBaselineConfigProvider, DefaultBaselineConfigProvider>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for baseline analyzer.
|
||||
/// </summary>
|
||||
public sealed class BaselineAnalyzerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional custom configurations to register.
|
||||
/// </summary>
|
||||
public List<EntryTraceBaselineConfig> CustomConfigurations { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include default configurations.
|
||||
/// </summary>
|
||||
public bool IncludeDefaults { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Global confidence threshold override.
|
||||
/// </summary>
|
||||
public double? GlobalConfidenceThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global timeout in seconds.
|
||||
/// </summary>
|
||||
public int? GlobalTimeoutSeconds { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides baseline configurations.
|
||||
/// </summary>
|
||||
public interface IBaselineConfigProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets configuration for the specified language.
|
||||
/// </summary>
|
||||
EntryTraceBaselineConfig? GetConfiguration(EntryTraceLanguage language);
|
||||
|
||||
/// <summary>
|
||||
/// Gets configuration by ID.
|
||||
/// </summary>
|
||||
EntryTraceBaselineConfig? GetConfiguration(string configId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available configurations.
|
||||
/// </summary>
|
||||
IReadOnlyList<EntryTraceBaselineConfig> GetAllConfigurations();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default baseline configuration provider.
|
||||
/// </summary>
|
||||
public sealed class DefaultBaselineConfigProvider : IBaselineConfigProvider
|
||||
{
|
||||
private readonly Dictionary<string, EntryTraceBaselineConfig> _configsById;
|
||||
private readonly Dictionary<EntryTraceLanguage, EntryTraceBaselineConfig> _configsByLanguage;
|
||||
|
||||
public DefaultBaselineConfigProvider()
|
||||
{
|
||||
var configs = DefaultConfigurations.All;
|
||||
|
||||
_configsById = configs.ToDictionary(c => c.ConfigId, StringComparer.OrdinalIgnoreCase);
|
||||
_configsByLanguage = configs.ToDictionary(c => c.Language);
|
||||
}
|
||||
|
||||
public EntryTraceBaselineConfig? GetConfiguration(EntryTraceLanguage language)
|
||||
{
|
||||
return _configsByLanguage.TryGetValue(language, out var config) ? config : null;
|
||||
}
|
||||
|
||||
public EntryTraceBaselineConfig? GetConfiguration(string configId)
|
||||
{
|
||||
return _configsById.TryGetValue(configId, out var config) ? config : null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<EntryTraceBaselineConfig> GetAllConfigurations()
|
||||
{
|
||||
return _configsById.Values.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.EntryTrace.Baseline;
|
||||
|
||||
/// <summary>
|
||||
/// Provides default baseline configurations for common languages and frameworks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implements SCANNER-ENTRYTRACE-18-508: Default entry point detection patterns.
|
||||
/// </remarks>
|
||||
public static class DefaultConfigurations
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all default configurations.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<EntryTraceBaselineConfig> All => new[]
|
||||
{
|
||||
JavaSpring,
|
||||
PythonFlaskDjango,
|
||||
NodeExpress,
|
||||
TypeScriptNestJs,
|
||||
DotNetAspNetCore,
|
||||
GoGin
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Java Spring Boot configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig JavaSpring => new()
|
||||
{
|
||||
ConfigId = "java-spring-baseline",
|
||||
Language = EntryTraceLanguage.Java,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-get-mapping",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@GetMapping\s*\(\s*[""']?(?<path>[^""'\)]+)[""']?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-post-mapping",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@PostMapping\s*\(\s*[""']?(?<path>[^""'\)]+)[""']?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-put-mapping",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@PutMapping\s*\(\s*[""']?(?<path>[^""'\)]+)[""']?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-delete-mapping",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@DeleteMapping\s*\(\s*[""']?(?<path>[^""'\)]+)[""']?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-request-mapping",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@RequestMapping\s*\([^)]*value\s*=\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-scheduled",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@Scheduled\s*\(",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.ScheduledJob,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-kafka-listener",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@KafkaListener\s*\(",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.MessageConsumer,
|
||||
Framework = "spring"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "spring-grpc-service",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"@GrpcService",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.GrpcMethod,
|
||||
Framework = "spring"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "spring-boot",
|
||||
Name = "Spring Boot",
|
||||
VersionRange = ">=2.0.0",
|
||||
DetectionPatterns = ImmutableArray.Create(
|
||||
"org.springframework.boot",
|
||||
"@SpringBootApplication",
|
||||
"spring-boot-starter"
|
||||
),
|
||||
EntryPatterns = ImmutableArray.Create(
|
||||
"spring-get-mapping",
|
||||
"spring-post-mapping",
|
||||
"spring-put-mapping",
|
||||
"spring-delete-mapping",
|
||||
"spring-request-mapping",
|
||||
"spring-scheduled"
|
||||
),
|
||||
RouterFilePatterns = ImmutableArray.Create(
|
||||
"**/controller/**/*.java",
|
||||
"**/rest/**/*.java",
|
||||
"**/api/**/*.java"
|
||||
),
|
||||
ControllerPatterns = ImmutableArray.Create(
|
||||
".*Controller$",
|
||||
".*Resource$"
|
||||
)
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/test/**", "**/generated/**"),
|
||||
ExcludePackages = ImmutableArray.Create("org.springframework.test"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Python Flask/Django configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig PythonFlaskDjango => new()
|
||||
{
|
||||
ConfigId = "python-web-baseline",
|
||||
Language = EntryTraceLanguage.Python,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "flask-route",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@(?:app|blueprint|bp)\.route\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "flask"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "flask-get",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@(?:app|blueprint|bp)\.get\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "flask"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "flask-post",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@(?:app|blueprint|bp)\.post\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "flask"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "django-path",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"path\s*\(\s*[""'](?<path>[^""']+)[""']\s*,",
|
||||
Confidence = 0.85,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "django"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "fastapi-route",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@(?:app|router)\.(?<method>get|post|put|delete|patch)\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "fastapi"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "celery-task",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@(?:celery\.)?task\s*\(",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.ScheduledJob,
|
||||
Framework = "celery"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "flask",
|
||||
Name = "Flask",
|
||||
DetectionPatterns = ImmutableArray.Create("from flask import", "Flask(__name__)"),
|
||||
EntryPatterns = ImmutableArray.Create("flask-route", "flask-get", "flask-post"),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/routes.py", "**/views.py", "**/api/**/*.py")
|
||||
},
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "django",
|
||||
Name = "Django",
|
||||
DetectionPatterns = ImmutableArray.Create("from django", "django.conf.urls"),
|
||||
EntryPatterns = ImmutableArray.Create("django-path"),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/urls.py", "**/views.py")
|
||||
},
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "fastapi",
|
||||
Name = "FastAPI",
|
||||
DetectionPatterns = ImmutableArray.Create("from fastapi import", "FastAPI()"),
|
||||
EntryPatterns = ImmutableArray.Create("fastapi-route"),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/routers/**/*.py", "**/api/**/*.py")
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/test*/**", "**/migrations/**"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Node.js Express configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig NodeExpress => new()
|
||||
{
|
||||
ConfigId = "node-express-baseline",
|
||||
Language = EntryTraceLanguage.JavaScript,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "express-get",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:app|router)\.get\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "express"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "express-post",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:app|router)\.post\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "express"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "express-put",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:app|router)\.put\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "express"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "express-delete",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:app|router)\.delete\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "express"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "fastify-route",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"fastify\.(?<method>get|post|put|delete|patch)\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "fastify"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "koa-router",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"router\.(?<method>get|post|put|delete|patch)\s*\(\s*['""](?<path>[^'""]+)['""]",
|
||||
Confidence = 0.85,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "koa"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "express",
|
||||
Name = "Express.js",
|
||||
DetectionPatterns = ImmutableArray.Create("require('express')", "from 'express'", "express()"),
|
||||
EntryPatterns = ImmutableArray.Create("express-get", "express-post", "express-put", "express-delete"),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/routes/**/*.js", "**/api/**/*.js", "**/controllers/**/*.js")
|
||||
},
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "fastify",
|
||||
Name = "Fastify",
|
||||
DetectionPatterns = ImmutableArray.Create("require('fastify')", "from 'fastify'"),
|
||||
EntryPatterns = ImmutableArray.Create("fastify-route")
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/node_modules/**", "**/dist/**", "**/build/**"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// TypeScript NestJS configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig TypeScriptNestJs => new()
|
||||
{
|
||||
ConfigId = "typescript-nestjs-baseline",
|
||||
Language = EntryTraceLanguage.TypeScript,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-get",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@Get\s*\(\s*['""]?(?<path>[^'"")\s]*)['""]?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-post",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@Post\s*\(\s*['""]?(?<path>[^'"")\s]*)['""]?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-put",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@Put\s*\(\s*['""]?(?<path>[^'"")\s]*)['""]?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-delete",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@Delete\s*\(\s*['""]?(?<path>[^'"")\s]*)['""]?\s*\)",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-message-pattern",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@MessagePattern\s*\(",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.MessageConsumer,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-event-pattern",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@EventPattern\s*\(",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.EventHandler,
|
||||
Framework = "nestjs"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "nestjs-grpc-method",
|
||||
Type = PatternType.Decorator,
|
||||
Pattern = @"@GrpcMethod\s*\(",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.GrpcMethod,
|
||||
Framework = "nestjs"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "nestjs",
|
||||
Name = "NestJS",
|
||||
DetectionPatterns = ImmutableArray.Create("@nestjs/common", "@Controller", "@Injectable"),
|
||||
EntryPatterns = ImmutableArray.Create(
|
||||
"nestjs-get", "nestjs-post", "nestjs-put", "nestjs-delete",
|
||||
"nestjs-message-pattern", "nestjs-event-pattern", "nestjs-grpc-method"
|
||||
),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/*.controller.ts"),
|
||||
ControllerPatterns = ImmutableArray.Create(".*Controller$")
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/node_modules/**", "**/dist/**"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// .NET ASP.NET Core configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig DotNetAspNetCore => new()
|
||||
{
|
||||
ConfigId = "dotnet-aspnet-baseline",
|
||||
Language = EntryTraceLanguage.CSharp,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-httpget",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"\[HttpGet\s*\(\s*[""']?(?<path>[^""'\]]*)[""']?\s*\)\]",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-httppost",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"\[HttpPost\s*\(\s*[""']?(?<path>[^""'\]]*)[""']?\s*\)\]",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-httpput",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"\[HttpPut\s*\(\s*[""']?(?<path>[^""'\]]*)[""']?\s*\)\]",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-httpdelete",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"\[HttpDelete\s*\(\s*[""']?(?<path>[^""'\]]*)[""']?\s*\)\]",
|
||||
Confidence = 0.95,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-route",
|
||||
Type = PatternType.Annotation,
|
||||
Pattern = @"\[Route\s*\(\s*[""'](?<path>[^""']+)[""']\s*\)\]",
|
||||
Confidence = 0.85,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "aspnet-minimal-map",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:app|endpoints)\.Map(?<method>Get|Post|Put|Delete|Patch)\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "aspnet-minimal"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "grpc-service",
|
||||
Type = PatternType.ClassName,
|
||||
Pattern = @"class\s+\w+\s*:\s*\w+\.(\w+)Base\b",
|
||||
Confidence = 0.85,
|
||||
EntryType = EntryPointType.GrpcMethod,
|
||||
Framework = "grpc"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "aspnet",
|
||||
Name = "ASP.NET Core",
|
||||
DetectionPatterns = ImmutableArray.Create(
|
||||
"Microsoft.AspNetCore",
|
||||
"ControllerBase",
|
||||
"[ApiController]"
|
||||
),
|
||||
EntryPatterns = ImmutableArray.Create(
|
||||
"aspnet-httpget", "aspnet-httppost", "aspnet-httpput",
|
||||
"aspnet-httpdelete", "aspnet-route", "aspnet-minimal-map"
|
||||
),
|
||||
RouterFilePatterns = ImmutableArray.Create("**/*Controller.cs", "**/Controllers/**/*.cs"),
|
||||
ControllerPatterns = ImmutableArray.Create(".*Controller$")
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/bin/**", "**/obj/**", "**/Migrations/**"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Go Gin/Echo configuration.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig GoGin => new()
|
||||
{
|
||||
ConfigId = "go-web-baseline",
|
||||
Language = EntryTraceLanguage.Go,
|
||||
Version = "1.0.0",
|
||||
EntryPointPatterns = ImmutableArray.Create(
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "gin-route",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"(?:r|router|g|group)\.(?<method>GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "gin"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "echo-route",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"e\.(?<method>GET|POST|PUT|DELETE|PATCH)\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "echo"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "chi-route",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"r\.(?<method>Get|Post|Put|Delete|Patch)\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.9,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "chi"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "http-handle",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"http\.Handle(?:Func)?\s*\(\s*[""'](?<path>[^""']+)[""']",
|
||||
Confidence = 0.8,
|
||||
EntryType = EntryPointType.HttpEndpoint,
|
||||
Framework = "net/http"
|
||||
},
|
||||
new EntryPointPattern
|
||||
{
|
||||
PatternId = "grpc-register",
|
||||
Type = PatternType.FunctionName,
|
||||
Pattern = @"Register\w+Server\s*\(",
|
||||
Confidence = 0.85,
|
||||
EntryType = EntryPointType.GrpcMethod,
|
||||
Framework = "grpc"
|
||||
}
|
||||
),
|
||||
FrameworkConfigs = ImmutableArray.Create(
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "gin",
|
||||
Name = "Gin",
|
||||
DetectionPatterns = ImmutableArray.Create("github.com/gin-gonic/gin", "gin.Default()", "gin.New()"),
|
||||
EntryPatterns = ImmutableArray.Create("gin-route")
|
||||
},
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "echo",
|
||||
Name = "Echo",
|
||||
DetectionPatterns = ImmutableArray.Create("github.com/labstack/echo", "echo.New()"),
|
||||
EntryPatterns = ImmutableArray.Create("echo-route")
|
||||
},
|
||||
new FrameworkConfig
|
||||
{
|
||||
FrameworkId = "chi",
|
||||
Name = "Chi",
|
||||
DetectionPatterns = ImmutableArray.Create("github.com/go-chi/chi"),
|
||||
EntryPatterns = ImmutableArray.Create("chi-route")
|
||||
}
|
||||
),
|
||||
Exclusions = new ExclusionConfig
|
||||
{
|
||||
ExcludePaths = ImmutableArray.Create("**/vendor/**", "**/testdata/**"),
|
||||
ExcludeTestFiles = true,
|
||||
ExcludeGenerated = true
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets configuration for a specific language.
|
||||
/// </summary>
|
||||
public static EntryTraceBaselineConfig? GetForLanguage(EntryTraceLanguage language)
|
||||
{
|
||||
return language switch
|
||||
{
|
||||
EntryTraceLanguage.Java => JavaSpring,
|
||||
EntryTraceLanguage.Python => PythonFlaskDjango,
|
||||
EntryTraceLanguage.JavaScript => NodeExpress,
|
||||
EntryTraceLanguage.TypeScript => TypeScriptNestJs,
|
||||
EntryTraceLanguage.CSharp => DotNetAspNetCore,
|
||||
EntryTraceLanguage.Go => GoGin,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user