feat(telemetry): add telemetry client and services for tracking events

- Implemented TelemetryClient to handle event queuing and flushing to the telemetry endpoint.
- Created TtfsTelemetryService for emitting specific telemetry events related to TTFS.
- Added tests for TelemetryClient to ensure event queuing and flushing functionality.
- Introduced models for reachability drift detection, including DriftResult and DriftedSink.
- Developed DriftApiService for interacting with the drift detection API.
- Updated FirstSignalCardComponent to emit telemetry events on signal appearance.
- Enhanced localization support for first signal component with i18n strings.
This commit is contained in:
master
2025-12-18 16:19:16 +02:00
parent 00d2c99af9
commit 811f35cba7
114 changed files with 13702 additions and 268 deletions

View File

@@ -0,0 +1,220 @@
// -----------------------------------------------------------------------------
// VulnSurface.cs
// Sprint: SPRINT_3700_0002_0001_vuln_surfaces_core
// Description: Core models for vulnerability surface computation.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.VulnSurfaces.Models;
/// <summary>
/// A vulnerability surface represents the specific methods that changed
/// between a vulnerable and fixed version of a package.
/// </summary>
public sealed record VulnSurface
{
/// <summary>
/// Database ID.
/// </summary>
[JsonPropertyName("surface_id")]
public long SurfaceId { get; init; }
/// <summary>
/// CVE ID (e.g., "CVE-2024-12345").
/// </summary>
[JsonPropertyName("cve_id")]
public required string CveId { get; init; }
/// <summary>
/// Package identifier (PURL format preferred).
/// </summary>
[JsonPropertyName("package_id")]
public required string PackageId { get; init; }
/// <summary>
/// Ecosystem (nuget, npm, maven, pypi).
/// </summary>
[JsonPropertyName("ecosystem")]
public required string Ecosystem { get; init; }
/// <summary>
/// Vulnerable version analyzed.
/// </summary>
[JsonPropertyName("vuln_version")]
public required string VulnVersion { get; init; }
/// <summary>
/// Fixed version used for diff.
/// </summary>
[JsonPropertyName("fixed_version")]
public required string FixedVersion { get; init; }
/// <summary>
/// Sink methods (vulnerable code locations).
/// </summary>
[JsonPropertyName("sinks")]
public IReadOnlyList<VulnSurfaceSink> Sinks { get; init; } = [];
/// <summary>
/// Number of trigger methods that can reach sinks.
/// </summary>
[JsonPropertyName("trigger_count")]
public int TriggerCount { get; init; }
/// <summary>
/// Surface computation status.
/// </summary>
[JsonPropertyName("status")]
public VulnSurfaceStatus Status { get; init; }
/// <summary>
/// Confidence score (0.0-1.0).
/// </summary>
[JsonPropertyName("confidence")]
public double Confidence { get; init; } = 1.0;
/// <summary>
/// When the surface was computed.
/// </summary>
[JsonPropertyName("computed_at")]
public DateTimeOffset ComputedAt { get; init; }
/// <summary>
/// Error message if computation failed.
/// </summary>
[JsonPropertyName("error")]
public string? Error { get; init; }
}
/// <summary>
/// A sink method - a specific method that was modified in the security fix.
/// </summary>
public sealed record VulnSurfaceSink
{
/// <summary>
/// Database ID.
/// </summary>
[JsonPropertyName("sink_id")]
public long SinkId { get; init; }
/// <summary>
/// Parent surface ID.
/// </summary>
[JsonPropertyName("surface_id")]
public long SurfaceId { get; init; }
/// <summary>
/// Normalized method key.
/// </summary>
[JsonPropertyName("method_key")]
public required string MethodKey { get; init; }
/// <summary>
/// Declaring type/class name.
/// </summary>
[JsonPropertyName("declaring_type")]
public required string DeclaringType { get; init; }
/// <summary>
/// Method name.
/// </summary>
[JsonPropertyName("method_name")]
public required string MethodName { get; init; }
/// <summary>
/// Method signature.
/// </summary>
[JsonPropertyName("signature")]
public string? Signature { get; init; }
/// <summary>
/// Type of change detected.
/// </summary>
[JsonPropertyName("change_type")]
public MethodChangeType ChangeType { get; init; }
/// <summary>
/// Hash of the method in vulnerable version.
/// </summary>
[JsonPropertyName("vuln_hash")]
public string? VulnHash { get; init; }
/// <summary>
/// Hash of the method in fixed version.
/// </summary>
[JsonPropertyName("fixed_hash")]
public string? FixedHash { get; init; }
/// <summary>
/// Whether this sink is directly exploitable.
/// </summary>
[JsonPropertyName("is_direct_exploit")]
public bool IsDirectExploit { get; init; }
}
/// <summary>
/// Status of vulnerability surface computation.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum VulnSurfaceStatus
{
/// <summary>
/// Computation pending.
/// </summary>
Pending,
/// <summary>
/// Computation in progress.
/// </summary>
Computing,
/// <summary>
/// Successfully computed.
/// </summary>
Computed,
/// <summary>
/// Computation failed.
/// </summary>
Failed,
/// <summary>
/// No diff detected (versions identical).
/// </summary>
NoDiff,
/// <summary>
/// Package not found.
/// </summary>
PackageNotFound
}
/// <summary>
/// Type of method change detected.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum MethodChangeType
{
/// <summary>
/// Method body was modified.
/// </summary>
Modified,
/// <summary>
/// Method was added in fixed version.
/// </summary>
Added,
/// <summary>
/// Method was removed in fixed version.
/// </summary>
Removed,
/// <summary>
/// Method signature changed.
/// </summary>
SignatureChanged
}

View File

@@ -0,0 +1,168 @@
// -----------------------------------------------------------------------------
// VulnSurfaceTrigger.cs
// Sprint: SPRINT_3700_0003_0001_trigger_extraction
// Description: Model for trigger methods that can reach vulnerable sinks.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.VulnSurfaces.Models;
/// <summary>
/// Represents a trigger method - a public API that can reach a vulnerable sink method.
/// </summary>
public sealed record VulnSurfaceTrigger
{
/// <summary>
/// Surface ID this trigger belongs to.
/// </summary>
[JsonPropertyName("surface_id")]
public long SurfaceId { get; init; }
/// <summary>
/// Unique key for the trigger method (public API).
/// Format: namespace.class::methodName(signature)
/// </summary>
[JsonPropertyName("trigger_method_key")]
public required string TriggerMethodKey { get; init; }
/// <summary>
/// Unique key for the sink method (vulnerable code location).
/// </summary>
[JsonPropertyName("sink_method_key")]
public required string SinkMethodKey { get; init; }
/// <summary>
/// Internal call path from trigger to sink within the package.
/// </summary>
[JsonPropertyName("internal_path")]
public IReadOnlyList<string>? InternalPath { get; init; }
/// <summary>
/// Whether this trigger was found via interface/base method expansion.
/// </summary>
[JsonPropertyName("is_interface_expansion")]
public bool IsInterfaceExpansion { get; init; }
/// <summary>
/// Depth from trigger to sink.
/// </summary>
[JsonPropertyName("depth")]
public int Depth { get; init; }
/// <summary>
/// Confidence score for this trigger path (0.0-1.0).
/// </summary>
[JsonPropertyName("confidence")]
public double Confidence { get; init; } = 1.0;
}
/// <summary>
/// Internal method reference within a call graph.
/// </summary>
public sealed record InternalMethodRef
{
/// <summary>
/// Fully qualified method key.
/// </summary>
public required string MethodKey { get; init; }
/// <summary>
/// Method name without namespace.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Declaring type name.
/// </summary>
public required string DeclaringType { get; init; }
/// <summary>
/// Whether this method is public.
/// </summary>
public bool IsPublic { get; init; }
/// <summary>
/// Whether this method is from an interface.
/// </summary>
public bool IsInterface { get; init; }
/// <summary>
/// Whether this method is virtual/abstract (can be overridden).
/// </summary>
public bool IsVirtual { get; init; }
/// <summary>
/// Signature parameters.
/// </summary>
public IReadOnlyList<string>? Parameters { get; init; }
/// <summary>
/// Return type.
/// </summary>
public string? ReturnType { get; init; }
}
/// <summary>
/// Edge in the internal call graph.
/// </summary>
public sealed record InternalCallEdge
{
/// <summary>
/// Caller method key.
/// </summary>
public required string Caller { get; init; }
/// <summary>
/// Callee method key.
/// </summary>
public required string Callee { get; init; }
/// <summary>
/// Call site offset (IL offset for .NET, bytecode offset for Java).
/// </summary>
public int? CallSiteOffset { get; init; }
/// <summary>
/// Whether this is a virtual/dispatch call.
/// </summary>
public bool IsVirtualCall { get; init; }
}
/// <summary>
/// Result of trigger extraction for a vulnerability surface.
/// </summary>
public sealed record TriggerExtractionResult
{
/// <summary>
/// Whether extraction succeeded.
/// </summary>
public bool Success { get; init; }
/// <summary>
/// Extracted triggers.
/// </summary>
public IReadOnlyList<VulnSurfaceTrigger> Triggers { get; init; } = [];
/// <summary>
/// Error message if extraction failed.
/// </summary>
public string? Error { get; init; }
/// <summary>
/// Number of public methods analyzed.
/// </summary>
public int PublicMethodsAnalyzed { get; init; }
/// <summary>
/// Number of internal edges in the call graph.
/// </summary>
public int InternalEdgeCount { get; init; }
/// <summary>
/// Extraction duration.
/// </summary>
public TimeSpan Duration { get; init; }
}