Add SBOM, symbols, traces, and VEX files for CVE-2022-21661 SQLi case
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created CycloneDX and SPDX SBOM files for both reachable and unreachable images.
- Added symbols.json detailing function entry and sink points in the WordPress code.
- Included runtime traces for function calls in both reachable and unreachable scenarios.
- Developed OpenVEX files indicating vulnerability status and justification for both cases.
- Updated README for evaluator harness to guide integration with scanner output.
This commit is contained in:
master
2025-11-08 20:53:45 +02:00
parent 515975edc5
commit 536f6249a6
837 changed files with 37279 additions and 14675 deletions

View File

@@ -9,9 +9,9 @@ using StellaOps.Signals.Models;
namespace StellaOps.Signals.Parsing;
/// <summary>
/// Simple JSON-based callgraph parser used for initial language coverage.
/// </summary>
internal sealed class SimpleJsonCallgraphParser : ICallgraphParser
/// Simple JSON-based callgraph parser used for initial language coverage.
/// </summary>
public sealed class SimpleJsonCallgraphParser : ICallgraphParser
{
private readonly JsonSerializerOptions serializerOptions;
@@ -27,93 +27,160 @@ internal sealed class SimpleJsonCallgraphParser : ICallgraphParser
public string Language { get; }
public async Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(artifactStream);
public async Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(artifactStream);
using var document = await JsonDocument.ParseAsync(artifactStream, cancellationToken: cancellationToken).ConfigureAwait(false);
var root = document.RootElement;
if (TryParseLegacy(root, out var legacyResult))
{
return legacyResult;
}
if (TryParseSchemaV1(root, out var schemaResult))
{
return schemaResult;
}
throw new CallgraphParserValidationException("Callgraph artifact payload is empty or missing required fields.");
}
private static bool TryParseLegacy(JsonElement root, out CallgraphParseResult result)
{
result = default!;
if (!root.TryGetProperty("graph", out var graphElement))
{
return false;
}
var nodesElement = graphElement.GetProperty("nodes");
var edgesElement = graphElement.TryGetProperty("edges", out var edgesValue) ? edgesValue : default;
var nodes = new List<CallgraphNode>(nodesElement.GetArrayLength());
foreach (var nodeElement in nodesElement.EnumerateArray())
{
var id = nodeElement.GetProperty("id").GetString();
if (string.IsNullOrWhiteSpace(id))
{
throw new CallgraphParserValidationException("Callgraph node is missing an id.");
}
nodes.Add(new CallgraphNode(
Id: id.Trim(),
Name: nodeElement.TryGetProperty("name", out var nameEl) ? nameEl.GetString() ?? id.Trim() : id.Trim(),
Kind: nodeElement.TryGetProperty("kind", out var kindEl) ? kindEl.GetString() ?? "function" : "function",
Namespace: nodeElement.TryGetProperty("namespace", out var nsEl) ? nsEl.GetString() : null,
File: nodeElement.TryGetProperty("file", out var fileEl) ? fileEl.GetString() : null,
Line: nodeElement.TryGetProperty("line", out var lineEl) && lineEl.ValueKind == JsonValueKind.Number ? lineEl.GetInt32() : null));
}
var edges = new List<CallgraphEdge>();
if (edgesElement.ValueKind == JsonValueKind.Array)
{
foreach (var edgeElement in edgesElement.EnumerateArray())
{
var source = edgeElement.GetProperty("source").GetString();
var target = edgeElement.GetProperty("target").GetString();
if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(target))
{
throw new CallgraphParserValidationException("Callgraph edge requires both source and target.");
}
var type = edgeElement.TryGetProperty("type", out var typeEl) ? typeEl.GetString() ?? "call" : "call";
edges.Add(new CallgraphEdge(source.Trim(), target.Trim(), type));
}
}
var formatVersion = root.TryGetProperty("formatVersion", out var versionEl)
? versionEl.GetString()
: null;
result = new CallgraphParseResult(nodes, edges, string.IsNullOrWhiteSpace(formatVersion) ? "1.0" : formatVersion!.Trim());
return true;
}
private static bool TryParseSchemaV1(JsonElement root, out CallgraphParseResult result)
{
result = default!;
if (!root.TryGetProperty("nodes", out var nodesElement) && !root.TryGetProperty("edges", out _))
{
return false;
}
var nodes = new List<CallgraphNode>();
if (nodesElement.ValueKind == JsonValueKind.Array)
{
foreach (var nodeElement in nodesElement.EnumerateArray())
{
var id = nodeElement.TryGetProperty("sid", out var sidEl) ? sidEl.GetString() : nodeElement.GetProperty("id").GetString();
if (string.IsNullOrWhiteSpace(id))
{
throw new CallgraphParserValidationException("Callgraph node is missing an id.");
}
nodes.Add(new CallgraphNode(
Id: id.Trim(),
Name: nodeElement.TryGetProperty("name", out var nameEl) ? nameEl.GetString() ?? id.Trim() : id.Trim(),
Kind: nodeElement.TryGetProperty("kind", out var kindEl) ? kindEl.GetString() ?? "function" : "function",
Namespace: nodeElement.TryGetProperty("namespace", out var nsEl) ? nsEl.GetString() : null,
File: nodeElement.TryGetProperty("file", out var fileEl) ? fileEl.GetString() : null,
Line: nodeElement.TryGetProperty("line", out var lineEl) && lineEl.ValueKind == JsonValueKind.Number ? lineEl.GetInt32() : null));
}
}
if (!root.TryGetProperty("edges", out var edgesElement) || edgesElement.ValueKind != JsonValueKind.Array)
{
edgesElement = default;
}
var edges = new List<CallgraphEdge>();
if (edgesElement.ValueKind == JsonValueKind.Array)
{
foreach (var edgeElement in edgesElement.EnumerateArray())
{
var from = edgeElement.TryGetProperty("from", out var fromEl) ? fromEl.GetString() : edgeElement.GetProperty("source").GetString();
var to = edgeElement.TryGetProperty("to", out var toEl) ? toEl.GetString() : edgeElement.GetProperty("target").GetString();
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
{
throw new CallgraphParserValidationException("Callgraph edge requires both source and target.");
}
var kind = edgeElement.TryGetProperty("kind", out var kindEl)
? kindEl.GetString() ?? "call"
: edgeElement.TryGetProperty("type", out var typeEl)
? typeEl.GetString() ?? "call"
: "call";
edges.Add(new CallgraphEdge(from.Trim(), to.Trim(), kind));
}
}
if (nodes.Count == 0)
{
// When nodes are omitted (framework overlay), derive them from the referenced edges.
var uniqueNodeIds = new HashSet<string>(StringComparer.Ordinal);
foreach (var edge in edges)
{
uniqueNodeIds.Add(edge.SourceId);
uniqueNodeIds.Add(edge.TargetId);
}
foreach (var nodeId in uniqueNodeIds)
{
nodes.Add(new CallgraphNode(nodeId, nodeId, "function", null, null, null));
}
}
var schemaVersion = root.TryGetProperty("schema_version", out var schemaEl)
? schemaEl.GetString()
: "1.0";
result = new CallgraphParseResult(nodes, edges, string.IsNullOrWhiteSpace(schemaVersion) ? "1.0" : schemaVersion!.Trim());
return true;
}
var payload = await JsonSerializer.DeserializeAsync<RawCallgraphPayload>(
artifactStream,
serializerOptions,
cancellationToken).ConfigureAwait(false);
if (payload is null)
{
throw new CallgraphParserValidationException("Callgraph artifact payload is empty.");
}
if (payload.Graph is null)
{
throw new CallgraphParserValidationException("Callgraph artifact is missing 'graph' section.");
}
if (payload.Graph.Nodes is null || payload.Graph.Nodes.Count == 0)
{
throw new CallgraphParserValidationException("Callgraph artifact must include at least one node.");
}
if (payload.Graph.Edges is null)
{
payload.Graph.Edges = new List<RawCallgraphEdge>();
}
var nodes = new List<CallgraphNode>(payload.Graph.Nodes.Count);
foreach (var node in payload.Graph.Nodes)
{
if (string.IsNullOrWhiteSpace(node.Id))
{
throw new CallgraphParserValidationException("Callgraph node is missing an id.");
}
nodes.Add(new CallgraphNode(
Id: node.Id.Trim(),
Name: node.Name ?? node.Id.Trim(),
Kind: node.Kind ?? "function",
Namespace: node.Namespace,
File: node.File,
Line: node.Line));
}
var edges = new List<CallgraphEdge>(payload.Graph.Edges.Count);
foreach (var edge in payload.Graph.Edges)
{
if (string.IsNullOrWhiteSpace(edge.Source) || string.IsNullOrWhiteSpace(edge.Target))
{
throw new CallgraphParserValidationException("Callgraph edge requires both source and target.");
}
edges.Add(new CallgraphEdge(edge.Source.Trim(), edge.Target.Trim(), edge.Type ?? "call"));
}
var formatVersion = string.IsNullOrWhiteSpace(payload.FormatVersion) ? "1.0" : payload.FormatVersion.Trim();
return new CallgraphParseResult(nodes, edges, formatVersion);
}
private sealed class RawCallgraphPayload
{
public string? FormatVersion { get; set; }
public RawCallgraphGraph? Graph { get; set; }
}
private sealed class RawCallgraphGraph
{
public List<RawCallgraphNode>? Nodes { get; set; }
public List<RawCallgraphEdge>? Edges { get; set; }
}
private sealed class RawCallgraphNode
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Kind { get; set; }
public string? Namespace { get; set; }
public string? File { get; set; }
public int? Line { get; set; }
}
private sealed class RawCallgraphEdge
{
public string? Source { get; set; }
public string? Target { get; set; }
public string? Type { get; set; }
}
}
}