Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,184 @@
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture.Timeline;
/// <summary>
/// Runtime observation timeline for a finding.
/// </summary>
public sealed record RuntimeTimeline
{
/// <summary>
/// Finding this timeline is for.
/// </summary>
public required Guid FindingId { get; init; }
/// <summary>
/// Vulnerable component being tracked.
/// </summary>
public required string ComponentPurl { get; init; }
/// <summary>
/// Time window start.
/// </summary>
public required DateTimeOffset WindowStart { get; init; }
/// <summary>
/// Time window end.
/// </summary>
public required DateTimeOffset WindowEnd { get; init; }
/// <summary>
/// Overall posture based on observations.
/// </summary>
public required RuntimePosture Posture { get; init; }
/// <summary>
/// Posture explanation.
/// </summary>
public required string PostureExplanation { get; init; }
/// <summary>
/// Time buckets with observation summaries.
/// </summary>
public required IReadOnlyList<TimelineBucket> Buckets { get; init; }
/// <summary>
/// Significant events in the timeline.
/// </summary>
public required IReadOnlyList<TimelineEvent> Events { get; init; }
/// <summary>
/// Total observation count.
/// </summary>
public int TotalObservations => Buckets.Sum(b => b.ObservationCount);
/// <summary>
/// Capture session digests.
/// </summary>
public required IReadOnlyList<string> SessionDigests { get; init; }
}
public enum RuntimePosture
{
/// <summary>No runtime data available.</summary>
Unknown,
/// <summary>Runtime evidence supports the verdict.</summary>
Supports,
/// <summary>Runtime evidence contradicts the verdict.</summary>
Contradicts,
/// <summary>Runtime evidence is inconclusive.</summary>
Inconclusive
}
/// <summary>
/// A time bucket in the timeline.
/// </summary>
public sealed record TimelineBucket
{
/// <summary>
/// Bucket start time.
/// </summary>
public required DateTimeOffset Start { get; init; }
/// <summary>
/// Bucket end time.
/// </summary>
public required DateTimeOffset End { get; init; }
/// <summary>
/// Number of observations in this bucket.
/// </summary>
public required int ObservationCount { get; init; }
/// <summary>
/// Observation types in this bucket.
/// </summary>
public required IReadOnlyList<ObservationTypeSummary> ByType { get; init; }
/// <summary>
/// Whether component was loaded in this bucket.
/// </summary>
public required bool ComponentLoaded { get; init; }
/// <summary>
/// Whether vulnerable code was executed.
/// </summary>
public bool? VulnerableCodeExecuted { get; init; }
}
/// <summary>
/// Summary of observations by type.
/// </summary>
public sealed record ObservationTypeSummary
{
public required ObservationType Type { get; init; }
public required int Count { get; init; }
}
public enum ObservationType
{
LibraryLoad,
Syscall,
NetworkConnection,
FileAccess,
ProcessSpawn,
SymbolResolution
}
/// <summary>
/// A significant event in the timeline.
/// </summary>
public sealed record TimelineEvent
{
/// <summary>
/// Event timestamp.
/// </summary>
public required DateTimeOffset Timestamp { get; init; }
/// <summary>
/// Event type.
/// </summary>
public required TimelineEventType Type { get; init; }
/// <summary>
/// Event description.
/// </summary>
public required string Description { get; init; }
/// <summary>
/// Significance level.
/// </summary>
public required EventSignificance Significance { get; init; }
/// <summary>
/// Related evidence digest.
/// </summary>
public string? EvidenceDigest { get; init; }
/// <summary>
/// Additional details.
/// </summary>
public IReadOnlyDictionary<string, string> Details { get; init; }
= new Dictionary<string, string>();
}
public enum TimelineEventType
{
ComponentLoaded,
ComponentUnloaded,
VulnerableFunctionCalled,
NetworkExposure,
SyscallBlocked,
ProcessForked,
CaptureStarted,
CaptureStopped
}
public enum EventSignificance
{
Low,
Medium,
High,
Critical
}

View File

@@ -0,0 +1,257 @@
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture.Timeline;
public interface ITimelineBuilder
{
RuntimeTimeline Build(
RuntimeEvidence evidence,
string componentPurl,
TimelineOptions options);
}
public sealed class TimelineBuilder : ITimelineBuilder
{
public RuntimeTimeline Build(
RuntimeEvidence evidence,
string componentPurl,
TimelineOptions options)
{
var windowStart = options.WindowStart ?? evidence.FirstObservation;
var windowEnd = options.WindowEnd ?? evidence.LastObservation;
// Build time buckets
var buckets = BuildBuckets(evidence, componentPurl, windowStart, windowEnd, options.BucketSize);
// Extract significant events
var events = ExtractEvents(evidence, componentPurl);
// Determine posture
var (posture, explanation) = DeterminePosture(buckets, events, componentPurl);
return new RuntimeTimeline
{
FindingId = Guid.Empty, // Set by caller
ComponentPurl = componentPurl,
WindowStart = windowStart,
WindowEnd = windowEnd,
Posture = posture,
PostureExplanation = explanation,
Buckets = buckets,
Events = events.OrderBy(e => e.Timestamp).ToList(),
SessionDigests = evidence.SessionDigests.ToList()
};
}
private List<TimelineBucket> BuildBuckets(
RuntimeEvidence evidence,
string componentPurl,
DateTimeOffset start,
DateTimeOffset end,
TimeSpan bucketSize)
{
var buckets = new List<TimelineBucket>();
var current = start;
while (current < end)
{
var bucketEnd = current + bucketSize;
if (bucketEnd > end) bucketEnd = end;
var observations = evidence.Observations
.Where(o => o.Timestamp >= current && o.Timestamp < bucketEnd)
.ToList();
var byType = observations
.GroupBy(o => ClassifyObservation(o))
.Select(g => new ObservationTypeSummary
{
Type = g.Key,
Count = g.Count()
})
.ToList();
var componentLoaded = observations.Any(o =>
o.Type == "library_load" &&
o.Path?.Contains(ExtractComponentName(componentPurl)) == true);
buckets.Add(new TimelineBucket
{
Start = current,
End = bucketEnd,
ObservationCount = observations.Count,
ByType = byType,
ComponentLoaded = componentLoaded,
VulnerableCodeExecuted = componentLoaded ? DetectVulnerableExecution(observations) : null
});
current = bucketEnd;
}
return buckets;
}
private List<TimelineEvent> ExtractEvents(RuntimeEvidence evidence, string componentPurl)
{
var events = new List<TimelineEvent>();
var componentName = ExtractComponentName(componentPurl);
foreach (var obs in evidence.Observations)
{
if (obs.Type == "library_load" && obs.Path?.Contains(componentName) == true)
{
events.Add(new TimelineEvent
{
Timestamp = obs.Timestamp,
Type = TimelineEventType.ComponentLoaded,
Description = $"Component {componentName} loaded",
Significance = EventSignificance.High,
EvidenceDigest = obs.Digest,
Details = new Dictionary<string, string>
{
["path"] = obs.Path ?? "",
["process_id"] = obs.ProcessId.ToString()
}
});
}
if (obs.Type == "network" && obs.Port is > 0 and < 1024)
{
events.Add(new TimelineEvent
{
Timestamp = obs.Timestamp,
Type = TimelineEventType.NetworkExposure,
Description = $"Network exposure on port {obs.Port}",
Significance = EventSignificance.Critical,
EvidenceDigest = obs.Digest
});
}
}
// Add capture session events
foreach (var session in evidence.Sessions)
{
events.Add(new TimelineEvent
{
Timestamp = session.StartTime,
Type = TimelineEventType.CaptureStarted,
Description = $"Capture session started ({session.Platform})",
Significance = EventSignificance.Low
});
if (session.EndTime.HasValue)
{
events.Add(new TimelineEvent
{
Timestamp = session.EndTime.Value,
Type = TimelineEventType.CaptureStopped,
Description = "Capture session stopped",
Significance = EventSignificance.Low
});
}
}
return events;
}
private static (RuntimePosture posture, string explanation) DeterminePosture(
List<TimelineBucket> buckets,
List<TimelineEvent> events,
string componentPurl)
{
if (buckets.Count == 0 || buckets.All(b => b.ObservationCount == 0))
{
return (RuntimePosture.Unknown, "No runtime observations collected");
}
var componentLoadedCount = buckets.Count(b => b.ComponentLoaded);
var totalBuckets = buckets.Count;
if (componentLoadedCount == 0)
{
return (RuntimePosture.Supports,
$"Component {ExtractComponentName(componentPurl)} was not loaded during observation window");
}
var hasNetworkExposure = events.Any(e => e.Type == TimelineEventType.NetworkExposure);
var hasVulnerableExecution = buckets.Any(b => b.VulnerableCodeExecuted == true);
if (hasVulnerableExecution || hasNetworkExposure)
{
return (RuntimePosture.Contradicts,
"Runtime evidence shows component is actively used and exposed");
}
if (componentLoadedCount < totalBuckets / 2)
{
return (RuntimePosture.Inconclusive,
$"Component loaded in {componentLoadedCount}/{totalBuckets} time periods");
}
return (RuntimePosture.Supports,
"Component loaded but no evidence of vulnerable code execution");
}
private static ObservationType ClassifyObservation(RuntimeObservation obs)
{
return obs.Type switch
{
"library_load" or "dlopen" => ObservationType.LibraryLoad,
"syscall" => ObservationType.Syscall,
"network" or "connect" => ObservationType.NetworkConnection,
"file" or "open" => ObservationType.FileAccess,
"fork" or "exec" => ObservationType.ProcessSpawn,
"symbol" => ObservationType.SymbolResolution,
_ => ObservationType.LibraryLoad
};
}
private static string ExtractComponentName(string purl)
{
// Extract name from PURL like pkg:npm/lodash@4.17.21
var parts = purl.Split('/');
var namePart = parts.LastOrDefault() ?? purl;
return namePart.Split('@').FirstOrDefault() ?? namePart;
}
private static bool? DetectVulnerableExecution(List<RuntimeObservation> observations)
{
// Check if any observation indicates vulnerable code path execution
return observations.Any(o =>
o.Type == "symbol" ||
o.Attributes?.ContainsKey("vulnerable_function") == true);
}
}
public sealed record TimelineOptions
{
public DateTimeOffset? WindowStart { get; init; }
public DateTimeOffset? WindowEnd { get; init; }
public TimeSpan BucketSize { get; init; } = TimeSpan.FromHours(1);
}
// Simplified runtime evidence types for Timeline API
public sealed record RuntimeEvidence
{
public required DateTimeOffset FirstObservation { get; init; }
public required DateTimeOffset LastObservation { get; init; }
public required IReadOnlyList<RuntimeObservation> Observations { get; init; }
public required IReadOnlyList<RuntimeSession> Sessions { get; init; }
public required IReadOnlyList<string> SessionDigests { get; init; }
}
public sealed record RuntimeObservation
{
public required DateTimeOffset Timestamp { get; init; }
public required string Type { get; init; }
public string? Path { get; init; }
public int? Port { get; init; }
public int ProcessId { get; init; }
public string? Digest { get; init; }
public IReadOnlyDictionary<string, string>? Attributes { get; init; }
}
public sealed record RuntimeSession
{
public required DateTimeOffset StartTime { get; init; }
public DateTimeOffset? EndTime { get; init; }
public required string Platform { get; init; }
}