This commit is contained in:
StellaOps Bot
2025-12-09 00:20:52 +02:00
parent 3d01bf9edc
commit bc0762e97d
261 changed files with 14033 additions and 4427 deletions

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using StellaOps.Signals.Models;
namespace StellaOps.Signals.Services;
internal static class ReachabilityFactDigestCalculator
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = false
};
public static string Compute(ReachabilityFactDocument fact)
{
ArgumentNullException.ThrowIfNull(fact);
var canonical = new CanonicalReachabilityFact(
CallgraphId: fact.CallgraphId ?? string.Empty,
SubjectKey: fact.SubjectKey ?? string.Empty,
Subject: new CanonicalSubject(
fact.Subject?.ImageDigest ?? string.Empty,
fact.Subject?.Component ?? string.Empty,
fact.Subject?.Version ?? string.Empty,
fact.Subject?.ScanId ?? string.Empty),
EntryPoints: NormalizeList(fact.EntryPoints),
States: NormalizeStates(fact.States),
RuntimeFacts: NormalizeRuntimeFacts(fact.RuntimeFacts),
Metadata: NormalizeMetadata(fact.Metadata),
Score: fact.Score,
UnknownsCount: fact.UnknownsCount,
UnknownsPressure: fact.UnknownsPressure,
ComputedAt: fact.ComputedAt);
var json = JsonSerializer.Serialize(canonical, SerializerOptions);
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
SHA256.HashData(Encoding.UTF8.GetBytes(json), hash);
return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
}
private static List<string> NormalizeList(IEnumerable<string>? values) =>
values?
.Where(v => !string.IsNullOrWhiteSpace(v))
.Select(v => v.Trim())
.Distinct(StringComparer.Ordinal)
.OrderBy(v => v, StringComparer.Ordinal)
.ToList() ?? new List<string>();
private static List<CanonicalState> NormalizeStates(IEnumerable<ReachabilityStateDocument>? states)
{
if (states is null)
{
return new List<CanonicalState>();
}
return states
.OrderBy(s => s.Target, StringComparer.Ordinal)
.Select(state => new CanonicalState(
Target: state.Target ?? string.Empty,
Reachable: state.Reachable,
Confidence: state.Confidence,
Bucket: state.Bucket ?? "unknown",
Weight: state.Weight,
Score: state.Score,
Path: NormalizeList(state.Path),
RuntimeHits: NormalizeList(state.Evidence?.RuntimeHits),
BlockedEdges: NormalizeList(state.Evidence?.BlockedEdges)))
.ToList();
}
private static List<CanonicalRuntimeFact> NormalizeRuntimeFacts(IEnumerable<RuntimeFactDocument>? facts)
{
if (facts is null)
{
return new List<CanonicalRuntimeFact>();
}
return facts
.Select(f => new CanonicalRuntimeFact(
SymbolId: f.SymbolId ?? string.Empty,
CodeId: f.CodeId,
SymbolDigest: f.SymbolDigest,
Purl: f.Purl,
BuildId: f.BuildId,
LoaderBase: f.LoaderBase,
ProcessId: f.ProcessId,
ProcessName: f.ProcessName,
SocketAddress: f.SocketAddress,
ContainerId: f.ContainerId,
EvidenceUri: f.EvidenceUri,
HitCount: f.HitCount,
ObservedAt: f.ObservedAt,
Metadata: NormalizeMetadata(f.Metadata)))
.OrderBy(f => f.SymbolId, StringComparer.Ordinal)
.ThenBy(f => f.CodeId, StringComparer.Ordinal)
.ThenBy(f => f.LoaderBase, StringComparer.Ordinal)
.ToList();
}
private static SortedDictionary<string, string?> NormalizeMetadata(IDictionary<string, string?>? metadata)
{
var normalized = new SortedDictionary<string, string?>(StringComparer.Ordinal);
if (metadata is null)
{
return normalized;
}
foreach (var kvp in metadata)
{
if (string.IsNullOrWhiteSpace(kvp.Key))
{
continue;
}
normalized[kvp.Key.Trim()] = kvp.Value?.Trim();
}
return normalized;
}
private sealed record CanonicalReachabilityFact(
string CallgraphId,
string SubjectKey,
CanonicalSubject Subject,
List<string> EntryPoints,
List<CanonicalState> States,
List<CanonicalRuntimeFact> RuntimeFacts,
SortedDictionary<string, string?> Metadata,
double Score,
int UnknownsCount,
double UnknownsPressure,
DateTimeOffset ComputedAt);
private sealed record CanonicalSubject(
string ImageDigest,
string Component,
string Version,
string ScanId);
private sealed record CanonicalState(
string Target,
bool Reachable,
double Confidence,
string Bucket,
double Weight,
double Score,
List<string> Path,
List<string> RuntimeHits,
List<string> BlockedEdges);
private sealed record CanonicalRuntimeFact(
string SymbolId,
string? CodeId,
string? SymbolDigest,
string? Purl,
string? BuildId,
string? LoaderBase,
int? ProcessId,
string? ProcessName,
string? SocketAddress,
string? ContainerId,
string? EvidenceUri,
int HitCount,
DateTimeOffset? ObservedAt,
SortedDictionary<string, string?> Metadata);
}