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,179 @@
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Scanner.Analyzers.Native.Observations;
namespace StellaOps.Scanner.Analyzers.Native.Reachability;
/// <summary>
/// Builds a deterministic reachability graph from native observations.
/// </summary>
internal static class NativeReachabilityGraphBuilder
{
private const string PayloadType = "stellaops.native.graph@1";
public static NativeReachabilityGraph Build(NativeObservationDocument document, string? layerDigest = null)
{
ArgumentNullException.ThrowIfNull(document);
ArgumentNullException.ThrowIfNull(document.Binary);
var nodes = new List<NativeReachabilityNode>();
var edges = new List<NativeReachabilityEdge>();
var binaryId = ResolveBinaryId(document);
var codeId = ResolveCodeId(document);
var binaryNodeId = $"bin::{binaryId}";
var rootNodeId = $"root::{binaryId}";
// Root node to capture synthetic sources (init arrays, entrypoints).
nodes.Add(new NativeReachabilityNode(
Id: rootNodeId,
Kind: "root",
Name: document.Binary.Path ?? "native-binary",
Path: document.Binary.Path,
BuildId: document.Binary.BuildId,
CodeId: codeId,
Format: document.Binary.Format,
Architecture: document.Binary.Architecture));
nodes.Add(new NativeReachabilityNode(
Id: binaryNodeId,
Kind: "binary",
Name: document.Binary.Path ?? "native-binary",
Path: document.Binary.Path,
BuildId: document.Binary.BuildId,
CodeId: codeId,
Format: document.Binary.Format,
Architecture: document.Binary.Architecture));
edges.Add(new NativeReachabilityEdge(rootNodeId, binaryNodeId, "binary"));
// Entrypoints -> binary (synthetic roots)
foreach (var entry in document.Entrypoints ?? Array.Empty<NativeObservationEntrypoint>())
{
var entryId = $"entry::{entry.Symbol ?? entry.Type ?? "entry"}";
nodes.Add(new NativeReachabilityNode(
Id: entryId,
Kind: "entrypoint",
Name: entry.Symbol ?? entry.Type ?? "entry",
Path: document.Binary.Path,
BuildId: document.Binary.BuildId,
CodeId: codeId,
Format: document.Binary.Format,
Architecture: document.Binary.Architecture));
edges.Add(new NativeReachabilityEdge(rootNodeId, entryId, "entrypoint"));
edges.Add(new NativeReachabilityEdge(entryId, binaryNodeId, "entrypoint"));
}
// Declared dependencies -> binary
foreach (var dep in document.DeclaredEdges ?? Array.Empty<NativeObservationDeclaredEdge>())
{
var targetId = $"decl::{dep.Target}";
nodes.Add(new NativeReachabilityNode(
Id: targetId,
Kind: "dependency",
Name: dep.Target ?? "unknown",
BuildId: null,
CodeId: null,
Format: null,
Path: dep.Target));
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, dep.Reason ?? "declared"));
}
// Heuristic/runtime edges as unknown targets
foreach (var edge in document.HeuristicEdges ?? Array.Empty<NativeObservationHeuristicEdge>())
{
var targetId = $"heur::{edge.Target}";
nodes.Add(new NativeReachabilityNode(
Id: targetId,
Kind: "dependency",
Name: edge.Target ?? "unknown",
Path: edge.Target));
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, edge.Reason ?? "heuristic"));
}
foreach (var edge in document.RuntimeEdges ?? Array.Empty<NativeObservationRuntimeEdge>())
{
var targetId = $"rt::{edge.Target}";
nodes.Add(new NativeReachabilityNode(
Id: targetId,
Kind: "runtime",
Name: edge.Target ?? "runtime",
Path: edge.Target));
edges.Add(new NativeReachabilityEdge(binaryNodeId, targetId, edge.Reason ?? "runtime"));
}
var distinctNodes = nodes
.GroupBy(n => n.Id, StringComparer.Ordinal)
.Select(g => g.First())
.OrderBy(n => n.Id, StringComparer.Ordinal)
.ToImmutableArray();
var distinctEdges = edges
.GroupBy(e => (e.From, e.To, e.Reason), ValueTuple.Create)
.Select(g => g.First())
.OrderBy(e => e.From, StringComparer.Ordinal)
.ThenBy(e => e.To, StringComparer.Ordinal)
.ThenBy(e => e.Reason, StringComparer.Ordinal)
.ToImmutableArray();
return new NativeReachabilityGraph(distinctNodes, distinctEdges, layerDigest, document.Binary.BuildId, codeId);
}
public static NativeReachabilityBundle ToBundle(NativeObservationDocument document, string? layerDigest = null)
{
var graph = Build(document, layerDigest);
return new NativeReachabilityBundle(
PayloadType,
graph,
layerDigest,
graph.BuildId,
graph.CodeId);
}
private static string ResolveBinaryId(NativeObservationDocument document)
{
if (!string.IsNullOrWhiteSpace(document.Binary.BuildId))
{
return $"buildid:{document.Binary.BuildId}";
}
if (!string.IsNullOrWhiteSpace(document.Binary.Sha256))
{
return $"sha256:{document.Binary.Sha256}";
}
return $"path:{document.Binary.Path}";
}
private static string? ResolveCodeId(NativeObservationDocument document)
{
if (!string.IsNullOrWhiteSpace(document.Binary.BuildId))
{
return document.Binary.BuildId;
}
if (!string.IsNullOrWhiteSpace(document.Binary.Sha256))
{
return document.Binary.Sha256;
}
if (!string.IsNullOrWhiteSpace(document.Binary.Path))
{
return ComputeSha256(document.Binary.Path);
}
return null;
}
private static string ComputeSha256(string value)
{
var bytes = Encoding.UTF8.GetBytes(value);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Immutable;
namespace StellaOps.Scanner.Analyzers.Native.Reachability;
internal sealed record NativeReachabilityNode(
string Id,
string Kind,
string Name,
string? BuildId = null,
string? CodeId = null,
string? Format = null,
string? Path = null,
string? Architecture = null);
internal sealed record NativeReachabilityEdge(
string From,
string To,
string Reason);
internal sealed record NativeReachabilityGraph(
ImmutableArray<NativeReachabilityNode> Nodes,
ImmutableArray<NativeReachabilityEdge> Edges,
string? LayerDigest,
string? BuildId,
string? CodeId);
internal sealed record NativeReachabilityBundle(
string PayloadType,
NativeReachabilityGraph Graph,
string? LayerDigest,
string? BuildId,
string? CodeId);