feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies. - Documented roles and guidelines in AGENTS.md for Scheduler module. - Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs. - Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics. - Developed API endpoints for managing resolver jobs and retrieving metrics. - Defined models for resolver job requests and responses. - Integrated dependency injection for resolver job services. - Implemented ImpactIndexSnapshot for persisting impact index data. - Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring. - Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService. - Created dotnet-filter.sh script to handle command-line arguments for dotnet. - Established nuget-prime project for managing package downloads.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
|
||||
|
||||
internal static class DenoPolicySignalEmitter
|
||||
{
|
||||
public static IReadOnlyDictionary<string, string> FromTrace(string observationHash, DenoRuntimeTraceMetadata metadata)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(observationHash);
|
||||
ArgumentNullException.ThrowIfNull(metadata);
|
||||
|
||||
var signals = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["surface.lang.deno.runtime.hash"] = observationHash,
|
||||
["surface.lang.deno.permissions"] = string.Join(',', metadata.UniquePermissions),
|
||||
["surface.lang.deno.remote_origins"] = string.Join(',', metadata.RemoteOrigins),
|
||||
["surface.lang.deno.npm_modules"] = metadata.NpmResolutions.ToString(CultureInfo.InvariantCulture),
|
||||
["surface.lang.deno.wasm_modules"] = metadata.WasmLoads.ToString(CultureInfo.InvariantCulture),
|
||||
["surface.lang.deno.dynamic_imports"] = metadata.DynamicImports.ToString(CultureInfo.InvariantCulture),
|
||||
["surface.lang.deno.module_loads"] = metadata.ModuleLoads.ToString(CultureInfo.InvariantCulture),
|
||||
["surface.lang.deno.permission_uses"] = metadata.PermissionUses.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
return signals;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
|
||||
|
||||
internal abstract record DenoRuntimeEvent(
|
||||
[property: JsonPropertyName("type")] string Type,
|
||||
[property: JsonPropertyName("ts")] DateTimeOffset Timestamp);
|
||||
|
||||
internal sealed record DenoModuleLoadEvent(
|
||||
DateTimeOffset Ts,
|
||||
DenoModuleIdentity Module,
|
||||
string Reason,
|
||||
IReadOnlyList<string> Permissions,
|
||||
string? Origin) : DenoRuntimeEvent("deno.module.load", Ts);
|
||||
|
||||
internal sealed record DenoPermissionUseEvent(
|
||||
DateTimeOffset Ts,
|
||||
string Permission,
|
||||
DenoModuleIdentity Module,
|
||||
string Details) : DenoRuntimeEvent("deno.permission.use", Ts);
|
||||
|
||||
internal sealed record DenoNpmResolutionEvent(
|
||||
DateTimeOffset Ts,
|
||||
string Specifier,
|
||||
string Package,
|
||||
string Version,
|
||||
string Resolved,
|
||||
bool Exists) : DenoRuntimeEvent("deno.npm.resolution", Ts);
|
||||
|
||||
internal sealed record DenoWasmLoadEvent(
|
||||
DateTimeOffset Ts,
|
||||
DenoModuleIdentity Module,
|
||||
string Importer,
|
||||
string Reason) : DenoRuntimeEvent("deno.wasm.load", Ts);
|
||||
|
||||
internal sealed record DenoModuleIdentity(
|
||||
[property: JsonPropertyName("normalized")] string Normalized,
|
||||
[property: JsonPropertyName("path_sha256")] string PathSha256);
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
|
||||
|
||||
internal static class DenoRuntimePathHasher
|
||||
{
|
||||
public static DenoModuleIdentity Create(string rootPath, string absolutePath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(rootPath);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(absolutePath);
|
||||
|
||||
var normalized = NormalizeRelative(rootPath, absolutePath);
|
||||
var sha = ComputeSha256(normalized);
|
||||
return new DenoModuleIdentity(normalized, sha);
|
||||
}
|
||||
|
||||
private static string NormalizeRelative(string rootPath, string absolutePath)
|
||||
{
|
||||
var relative = Path.GetRelativePath(rootPath, absolutePath);
|
||||
if (string.IsNullOrWhiteSpace(relative) || relative == ".")
|
||||
{
|
||||
return ".";
|
||||
}
|
||||
|
||||
return relative.Replace('\\', '/');
|
||||
}
|
||||
|
||||
private static string ComputeSha256(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Collects runtime events from the Deno harness and emits deterministic NDJSON payloads.
|
||||
/// </summary>
|
||||
internal sealed class DenoRuntimeTraceRecorder
|
||||
{
|
||||
private readonly List<DenoRuntimeEvent> _events = new();
|
||||
private readonly string _rootPath;
|
||||
|
||||
public DenoRuntimeTraceRecorder(string rootPath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(rootPath);
|
||||
_rootPath = Path.GetFullPath(rootPath);
|
||||
}
|
||||
|
||||
public void AddModuleLoad(string absoluteModulePath, string reason, IEnumerable<string> permissions, string? origin = null, DateTimeOffset? timestamp = null)
|
||||
{
|
||||
var identity = DenoRuntimePathHasher.Create(_rootPath, absoluteModulePath);
|
||||
var evt = new DenoModuleLoadEvent(
|
||||
Ts: timestamp ?? DateTimeOffset.UtcNow,
|
||||
Module: identity,
|
||||
Reason: reason ?? string.Empty,
|
||||
Permissions: NormalizePermissions(permissions),
|
||||
Origin: string.IsNullOrWhiteSpace(origin) ? null : origin);
|
||||
_events.Add(evt);
|
||||
}
|
||||
|
||||
public void AddPermissionUse(string absoluteModulePath, string permission, string details, DateTimeOffset? timestamp = null)
|
||||
{
|
||||
var identity = DenoRuntimePathHasher.Create(_rootPath, absoluteModulePath);
|
||||
var evt = new DenoPermissionUseEvent(
|
||||
Ts: timestamp ?? DateTimeOffset.UtcNow,
|
||||
Permission: permission ?? string.Empty,
|
||||
Module: identity,
|
||||
Details: details ?? string.Empty);
|
||||
_events.Add(evt);
|
||||
}
|
||||
|
||||
public void AddNpmResolution(string specifier, string package, string version, string resolved, bool exists, DateTimeOffset? timestamp = null)
|
||||
{
|
||||
_events.Add(new DenoNpmResolutionEvent(
|
||||
Ts: timestamp ?? DateTimeOffset.UtcNow,
|
||||
Specifier: specifier ?? string.Empty,
|
||||
Package: package ?? string.Empty,
|
||||
Version: version ?? string.Empty,
|
||||
Resolved: resolved ?? string.Empty,
|
||||
Exists: exists));
|
||||
}
|
||||
|
||||
public void AddWasmLoad(string absoluteModulePath, string importerRelativePath, string reason, DateTimeOffset? timestamp = null)
|
||||
{
|
||||
var identity = DenoRuntimePathHasher.Create(_rootPath, absoluteModulePath);
|
||||
_events.Add(new DenoWasmLoadEvent(
|
||||
Ts: timestamp ?? DateTimeOffset.UtcNow,
|
||||
Module: identity,
|
||||
Importer: importerRelativePath ?? string.Empty,
|
||||
Reason: reason ?? string.Empty));
|
||||
}
|
||||
|
||||
public DenoRuntimeTraceSnapshot Build()
|
||||
{
|
||||
var (content, hash, metadata) = DenoRuntimeTraceSerializer.Serialize(_events);
|
||||
return new DenoRuntimeTraceSnapshot(content, hash, metadata);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> NormalizePermissions(IEnumerable<string> permissions)
|
||||
{
|
||||
if (permissions is null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return permissions
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p.Trim().ToLowerInvariant())
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(p => p, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record DenoRuntimeTraceSnapshot(
|
||||
byte[] Content,
|
||||
string Sha256,
|
||||
DenoRuntimeTraceMetadata Metadata)
|
||||
{
|
||||
public ImmutableArray<byte> ContentImmutable => Content.ToImmutableArray();
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
|
||||
|
||||
internal static class DenoRuntimeTraceSerializer
|
||||
{
|
||||
private static readonly JsonWriterOptions WriterOptions = new()
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Indented = false
|
||||
};
|
||||
|
||||
public static (byte[] Content, string Sha256, DenoRuntimeTraceMetadata Metadata) Serialize(
|
||||
IEnumerable<DenoRuntimeEvent> events)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(events);
|
||||
|
||||
var ordered = events
|
||||
.OrderBy(e => e.Timestamp)
|
||||
.ThenBy(e => e.Type, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new Utf8JsonWriter(stream, WriterOptions))
|
||||
{
|
||||
foreach (var evt in ordered)
|
||||
{
|
||||
WriteEvent(writer, evt);
|
||||
writer.Flush();
|
||||
stream.WriteByte((byte)'\n');
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = stream.ToArray();
|
||||
var hash = ComputeSha256(bytes);
|
||||
var metadata = ComputeMetadata(ordered);
|
||||
return (bytes, hash, metadata);
|
||||
}
|
||||
|
||||
private static void WriteEvent(Utf8JsonWriter writer, DenoRuntimeEvent evt)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", evt.Type);
|
||||
writer.WriteString("ts", evt.Timestamp.ToUniversalTime());
|
||||
|
||||
switch (evt)
|
||||
{
|
||||
case DenoModuleLoadEvent e:
|
||||
WriteModule(writer, e.Module);
|
||||
writer.WriteString("reason", e.Reason);
|
||||
writer.WriteStartArray("permissions");
|
||||
foreach (var p in e.Permissions.OrderBy(p => p, StringComparer.Ordinal))
|
||||
{
|
||||
writer.WriteStringValue(p);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
if (!string.IsNullOrWhiteSpace(e.Origin))
|
||||
{
|
||||
writer.WriteString("origin", e.Origin);
|
||||
}
|
||||
break;
|
||||
|
||||
case DenoPermissionUseEvent e:
|
||||
writer.WriteString("permission", e.Permission);
|
||||
WriteModule(writer, e.Module);
|
||||
if (!string.IsNullOrWhiteSpace(e.Details))
|
||||
{
|
||||
writer.WriteString("details", e.Details);
|
||||
}
|
||||
break;
|
||||
|
||||
case DenoNpmResolutionEvent e:
|
||||
writer.WriteString("specifier", e.Specifier);
|
||||
writer.WriteString("package", e.Package);
|
||||
writer.WriteString("version", e.Version);
|
||||
writer.WriteString("resolved", e.Resolved);
|
||||
writer.WriteBoolean("exists", e.Exists);
|
||||
break;
|
||||
|
||||
case DenoWasmLoadEvent e:
|
||||
WriteModule(writer, e.Module);
|
||||
writer.WriteString("importer", e.Importer);
|
||||
writer.WriteString("reason", e.Reason);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported runtime event type '{evt.GetType().Name}'.");
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteModule(Utf8JsonWriter writer, DenoModuleIdentity module)
|
||||
{
|
||||
writer.WritePropertyName("module");
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("normalized", module.Normalized);
|
||||
writer.WriteString("path_sha256", module.PathSha256);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static string ComputeSha256(byte[] content)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(content ?? Array.Empty<byte>());
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static DenoRuntimeTraceMetadata ComputeMetadata(IReadOnlyCollection<DenoRuntimeEvent> events)
|
||||
{
|
||||
var moduleLoads = 0;
|
||||
var permissionUses = 0;
|
||||
var origins = new HashSet<string>(StringComparer.Ordinal);
|
||||
var permissions = new HashSet<string>(StringComparer.Ordinal);
|
||||
var npmResolutions = 0;
|
||||
var wasmLoads = 0;
|
||||
var dynamicImports = 0;
|
||||
|
||||
foreach (var evt in events)
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case DenoModuleLoadEvent e:
|
||||
moduleLoads++;
|
||||
if (!string.IsNullOrWhiteSpace(e.Origin))
|
||||
{
|
||||
origins.Add(e.Origin!);
|
||||
}
|
||||
if (string.Equals(e.Reason, "dynamic-import", StringComparison.Ordinal))
|
||||
{
|
||||
dynamicImports++;
|
||||
}
|
||||
foreach (var p in e.Permissions ?? Array.Empty<string>())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(p))
|
||||
{
|
||||
permissions.Add(p.Trim().ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DenoPermissionUseEvent:
|
||||
permissionUses++;
|
||||
break;
|
||||
case DenoNpmResolutionEvent:
|
||||
npmResolutions++;
|
||||
break;
|
||||
case DenoWasmLoadEvent:
|
||||
wasmLoads++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new DenoRuntimeTraceMetadata(
|
||||
EventCount: events.Count,
|
||||
ModuleLoads: moduleLoads,
|
||||
PermissionUses: permissionUses,
|
||||
RemoteOrigins: origins.OrderBy(o => o, StringComparer.Ordinal).ToArray(),
|
||||
UniquePermissions: permissions.OrderBy(p => p, StringComparer.Ordinal).ToArray(),
|
||||
NpmResolutions: npmResolutions,
|
||||
WasmLoads: wasmLoads,
|
||||
DynamicImports: dynamicImports);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record DenoRuntimeTraceMetadata(
|
||||
int EventCount,
|
||||
int ModuleLoads,
|
||||
int PermissionUses,
|
||||
IReadOnlyList<string> RemoteOrigins,
|
||||
IReadOnlyList<string> UniquePermissions,
|
||||
int NpmResolutions,
|
||||
int WasmLoads,
|
||||
int DynamicImports);
|
||||
Reference in New Issue
Block a user