up
Some checks failed
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 09:37:15 +02:00
parent e00f6365da
commit 6e45066e37
349 changed files with 17160 additions and 1867 deletions

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Signals.Models;
using StellaOps.Signals.Models.ReachabilityStore;
namespace StellaOps.Signals.Persistence;
public interface IReachabilityStoreRepository
{
Task UpsertGraphAsync(
string graphHash,
IReadOnlyCollection<CallgraphNode> nodes,
IReadOnlyCollection<CallgraphEdge> edges,
CancellationToken cancellationToken);
Task<IReadOnlyList<FuncNodeDocument>> GetFuncNodesByGraphAsync(string graphHash, CancellationToken cancellationToken);
Task<IReadOnlyList<CallEdgeDocument>> GetCallEdgesByGraphAsync(string graphHash, CancellationToken cancellationToken);
Task UpsertCveFuncHitsAsync(IReadOnlyCollection<CveFuncHitDocument> hits, CancellationToken cancellationToken);
Task<IReadOnlyList<CveFuncHitDocument>> GetCveFuncHitsBySubjectAsync(
string subjectKey,
string cveId,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,250 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Signals.Models;
using StellaOps.Signals.Models.ReachabilityStore;
namespace StellaOps.Signals.Persistence;
internal sealed class InMemoryReachabilityStoreRepository : IReachabilityStoreRepository
{
private readonly TimeProvider timeProvider;
private readonly ConcurrentDictionary<string, FuncNodeDocument> funcNodes = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, CallEdgeDocument> callEdges = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, CveFuncHitDocument> cveFuncHits = new(StringComparer.Ordinal);
public InMemoryReachabilityStoreRepository(TimeProvider timeProvider)
{
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public Task UpsertGraphAsync(
string graphHash,
IReadOnlyCollection<CallgraphNode> nodes,
IReadOnlyCollection<CallgraphEdge> edges,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(graphHash);
ArgumentNullException.ThrowIfNull(nodes);
ArgumentNullException.ThrowIfNull(edges);
var normalizedGraphHash = graphHash.Trim();
var now = timeProvider.GetUtcNow();
foreach (var node in nodes)
{
var document = ToFuncNodeDocument(normalizedGraphHash, node, now);
funcNodes[document.Id] = document;
}
foreach (var edge in edges)
{
var document = ToCallEdgeDocument(normalizedGraphHash, edge, now);
callEdges[document.Id] = document;
}
return Task.CompletedTask;
}
public Task<IReadOnlyList<FuncNodeDocument>> GetFuncNodesByGraphAsync(string graphHash, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(graphHash))
{
return Task.FromResult((IReadOnlyList<FuncNodeDocument>)Array.Empty<FuncNodeDocument>());
}
var normalizedGraphHash = graphHash.Trim();
var results = funcNodes.Values
.Where(doc => string.Equals(doc.GraphHash, normalizedGraphHash, StringComparison.Ordinal))
.Select(Clone)
.OrderBy(doc => doc.SymbolId, StringComparer.Ordinal)
.ThenBy(doc => doc.Purl, StringComparer.Ordinal)
.ThenBy(doc => doc.SymbolDigest, StringComparer.Ordinal)
.ToList();
return Task.FromResult((IReadOnlyList<FuncNodeDocument>)results);
}
public Task<IReadOnlyList<CallEdgeDocument>> GetCallEdgesByGraphAsync(string graphHash, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(graphHash))
{
return Task.FromResult((IReadOnlyList<CallEdgeDocument>)Array.Empty<CallEdgeDocument>());
}
var normalizedGraphHash = graphHash.Trim();
var results = callEdges.Values
.Where(doc => string.Equals(doc.GraphHash, normalizedGraphHash, StringComparison.Ordinal))
.Select(Clone)
.OrderBy(doc => doc.SourceId, StringComparer.Ordinal)
.ThenBy(doc => doc.TargetId, StringComparer.Ordinal)
.ThenBy(doc => doc.Type, StringComparer.Ordinal)
.ToList();
return Task.FromResult((IReadOnlyList<CallEdgeDocument>)results);
}
public Task UpsertCveFuncHitsAsync(IReadOnlyCollection<CveFuncHitDocument> hits, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(hits);
foreach (var hit in hits.Where(h => h is not null))
{
if (string.IsNullOrWhiteSpace(hit.SubjectKey) || string.IsNullOrWhiteSpace(hit.CveId))
{
continue;
}
var id = BuildCveFuncHitId(hit.SubjectKey, hit.CveId, hit.Purl, hit.SymbolDigest);
var clone = Clone(hit);
clone.Id = id;
cveFuncHits[id] = clone;
}
return Task.CompletedTask;
}
public Task<IReadOnlyList<CveFuncHitDocument>> GetCveFuncHitsBySubjectAsync(
string subjectKey,
string cveId,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(subjectKey) || string.IsNullOrWhiteSpace(cveId))
{
return Task.FromResult((IReadOnlyList<CveFuncHitDocument>)Array.Empty<CveFuncHitDocument>());
}
var normalizedSubjectKey = subjectKey.Trim();
var normalizedCve = cveId.Trim();
var results = cveFuncHits.Values
.Where(doc =>
string.Equals(doc.SubjectKey, normalizedSubjectKey, StringComparison.Ordinal) &&
string.Equals(doc.CveId, normalizedCve, StringComparison.OrdinalIgnoreCase))
.Select(Clone)
.OrderBy(doc => doc.CveId, StringComparer.OrdinalIgnoreCase)
.ThenBy(doc => doc.Purl, StringComparer.Ordinal)
.ThenBy(doc => doc.SymbolDigest, StringComparer.Ordinal)
.ToList();
return Task.FromResult((IReadOnlyList<CveFuncHitDocument>)results);
}
private static FuncNodeDocument ToFuncNodeDocument(string graphHash, CallgraphNode node, DateTimeOffset ingestedAt)
{
var symbolId = node.Id?.Trim() ?? string.Empty;
var id = BuildFuncNodeId(graphHash, symbolId);
return new FuncNodeDocument
{
Id = id,
GraphHash = graphHash,
SymbolId = symbolId,
Name = node.Name?.Trim() ?? string.Empty,
Kind = node.Kind?.Trim() ?? string.Empty,
Namespace = node.Namespace?.Trim(),
File = node.File?.Trim(),
Line = node.Line,
Purl = node.Purl?.Trim(),
SymbolDigest = node.SymbolDigest?.Trim()?.ToLowerInvariant(),
BuildId = node.BuildId?.Trim(),
CodeId = node.CodeId?.Trim(),
Language = node.Language?.Trim(),
Evidence = node.Evidence?.Where(v => !string.IsNullOrWhiteSpace(v)).Select(v => v.Trim()).OrderBy(v => v, StringComparer.Ordinal).ToList(),
Analyzer = node.Analyzer is null
? null
: node.Analyzer
.Where(kv => !string.IsNullOrWhiteSpace(kv.Key))
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.ToDictionary(kv => kv.Key.Trim(), kv => kv.Value?.Trim(), StringComparer.Ordinal),
IngestedAt = ingestedAt
};
}
private static CallEdgeDocument ToCallEdgeDocument(string graphHash, CallgraphEdge edge, DateTimeOffset ingestedAt)
{
var sourceId = edge.SourceId?.Trim() ?? string.Empty;
var targetId = edge.TargetId?.Trim() ?? string.Empty;
var type = edge.Type?.Trim() ?? string.Empty;
var id = BuildCallEdgeId(graphHash, sourceId, targetId, type);
return new CallEdgeDocument
{
Id = id,
GraphHash = graphHash,
SourceId = sourceId,
TargetId = targetId,
Type = type,
Purl = edge.Purl?.Trim(),
SymbolDigest = edge.SymbolDigest?.Trim()?.ToLowerInvariant(),
Candidates = edge.Candidates?.Where(v => !string.IsNullOrWhiteSpace(v)).Select(v => v.Trim()).OrderBy(v => v, StringComparer.Ordinal).ToList(),
Confidence = edge.Confidence,
Evidence = edge.Evidence?.Where(v => !string.IsNullOrWhiteSpace(v)).Select(v => v.Trim()).OrderBy(v => v, StringComparer.Ordinal).ToList(),
IngestedAt = ingestedAt
};
}
private static string BuildFuncNodeId(string graphHash, string symbolId)
=> $"{graphHash}|{symbolId}";
private static string BuildCallEdgeId(string graphHash, string sourceId, string targetId, string type)
=> $"{graphHash}|{sourceId}->{targetId}|{type}";
private static string BuildCveFuncHitId(string subjectKey, string cveId, string? purl, string? symbolDigest)
=> $"{subjectKey.Trim()}|{cveId.Trim().ToUpperInvariant()}|{purl?.Trim() ?? string.Empty}|{symbolDigest?.Trim()?.ToLowerInvariant() ?? string.Empty}";
private static FuncNodeDocument Clone(FuncNodeDocument source) => new()
{
Id = source.Id,
GraphHash = source.GraphHash,
SymbolId = source.SymbolId,
Name = source.Name,
Kind = source.Kind,
Namespace = source.Namespace,
File = source.File,
Line = source.Line,
Purl = source.Purl,
SymbolDigest = source.SymbolDigest,
BuildId = source.BuildId,
CodeId = source.CodeId,
Language = source.Language,
Evidence = source.Evidence?.ToList(),
Analyzer = source.Analyzer is null ? null : new Dictionary<string, string?>(source.Analyzer, StringComparer.Ordinal),
IngestedAt = source.IngestedAt
};
private static CallEdgeDocument Clone(CallEdgeDocument source) => new()
{
Id = source.Id,
GraphHash = source.GraphHash,
SourceId = source.SourceId,
TargetId = source.TargetId,
Type = source.Type,
Purl = source.Purl,
SymbolDigest = source.SymbolDigest,
Candidates = source.Candidates?.ToList(),
Confidence = source.Confidence,
Evidence = source.Evidence?.ToList(),
IngestedAt = source.IngestedAt
};
private static CveFuncHitDocument Clone(CveFuncHitDocument source) => new()
{
Id = source.Id,
SubjectKey = source.SubjectKey,
CveId = source.CveId,
GraphHash = source.GraphHash,
Purl = source.Purl,
SymbolDigest = source.SymbolDigest,
Reachable = source.Reachable,
Confidence = source.Confidence,
LatticeState = source.LatticeState,
EvidenceUris = source.EvidenceUris?.ToList(),
ComputedAt = source.ComputedAt
};
}