Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/DenoLanguageAnalyzer.cs
StellaOps Bot 48702191be
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat(graph-api): Add schema review notes for upcoming Graph API changes
feat(sbomservice): Add placeholder for SHA256SUMS in LNM v1 fixtures

docs(devportal): Create README for SDK archives in public directory

build(devportal): Implement offline bundle build script

test(devportal): Add link checker script for validating links in documentation

test(devportal): Create performance check script for dist folder size

test(devportal): Implement accessibility check script using Playwright and Axe

docs(devportal): Add SDK quickstart guide with examples for Node.js, Python, and cURL

feat(excititor): Implement MongoDB storage for airgap import records

test(findings): Add unit tests for export filters hash determinism

feat(findings): Define attestation contracts for ledger web service

feat(graph): Add MongoDB options and service collection extensions for graph indexing

test(graph): Implement integration tests for MongoDB provider and service collection extensions

feat(zastava): Define configuration options for Zastava surface secrets

build(tests): Create script to run Concelier linkset tests with TRX output
2025-11-22 19:22:30 +02:00

193 lines
7.8 KiB
C#

using System.Collections.Generic;
using System.Globalization;
using System.Text;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Observations;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Analyzers.Lang.Deno;
public sealed class DenoLanguageAnalyzer : ILanguageAnalyzer
{
public string Id => "deno";
public string DisplayName => "Deno Analyzer";
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(writer);
await TryWriteRuntimeShimAsync(context, cancellationToken).ConfigureAwait(false);
// Optional runtime capture: executes only when STELLA_DENO_ENTRYPOINT is provided.
await DenoRuntimeTraceRunner.TryExecuteAsync(context, logger: null, cancellationToken).ConfigureAwait(false);
var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, cancellationToken).ConfigureAwait(false);
var moduleGraph = DenoModuleGraphResolver.Resolve(workspace, cancellationToken);
var compatibility = DenoNpmCompatibilityAdapter.Analyze(workspace, moduleGraph, cancellationToken);
var bundleScan = DenoBundleScanner.Scan(context.RootPath, cancellationToken);
var bundleObservations = DenoBundleScanner.ToObservations(bundleScan);
var containerInputs = DenoContainerAdapter.CollectInputs(workspace, bundleObservations);
var containerRecords = DenoContainerEmitter.BuildRecords(Id, containerInputs);
writer.AddRange(containerRecords);
var observationDocument = DenoObservationBuilder.Build(moduleGraph, compatibility, bundleObservations);
var observationJson = DenoObservationSerializer.Serialize(observationDocument);
var observationHash = DenoObservationSerializer.ComputeSha256(observationJson);
var observationBytes = Encoding.UTF8.GetBytes(observationJson);
var observationMetadata = new[]
{
new KeyValuePair<string, string?>("deno.observation.hash", observationHash),
new KeyValuePair<string, string?>("deno.observation.entrypoints", observationDocument.Entrypoints.Length.ToString(CultureInfo.InvariantCulture)),
new KeyValuePair<string, string?>("deno.observation.capabilities", observationDocument.Capabilities.Length.ToString(CultureInfo.InvariantCulture)),
new KeyValuePair<string, string?>("deno.observation.bundles", observationDocument.Bundles.Length.ToString(CultureInfo.InvariantCulture))
};
TryPersistObservation(context, observationBytes, observationMetadata);
var observationEvidence = new[]
{
new LanguageComponentEvidence(
LanguageEvidenceKind.Derived,
"deno.observation",
"document",
observationJson,
observationHash)
};
writer.AddFromExplicitKey(
analyzerId: Id,
componentKey: "observation::deno",
purl: null,
name: "Deno Observation Summary",
version: null,
type: "deno-observation",
metadata: observationMetadata,
evidence: observationEvidence);
TryIngestRuntimeTrace(context);
// Task 5+ will convert moduleGraph + compatibility and bundle insights into SBOM components and evidence records.
GC.KeepAlive(moduleGraph);
GC.KeepAlive(compatibility);
GC.KeepAlive(bundleObservations);
GC.KeepAlive(containerInputs);
GC.KeepAlive(observationDocument);
}
private static async ValueTask TryWriteRuntimeShimAsync(LanguageAnalyzerContext context, CancellationToken cancellationToken)
{
try
{
await DenoRuntimeShim.WriteAsync(context.RootPath, cancellationToken).ConfigureAwait(false);
}
catch
{
// Shim is best-effort; failure should not block static analysis.
}
}
private void TryPersistObservation(
LanguageAnalyzerContext context,
byte[] observationBytes,
IEnumerable<KeyValuePair<string, string?>> metadata)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(observationBytes);
if (context.AnalysisStore is not { } analysisStore)
{
return;
}
var metadataDictionary = CreateMetadata(metadata);
var payload = new AnalyzerObservationPayload(
analyzerId: Id,
kind: "deno.observation",
mediaType: "application/json",
content: observationBytes,
metadata: metadataDictionary,
view: "observations");
analysisStore.Set(ScanAnalysisKeys.DenoObservationPayload, payload);
}
private static IReadOnlyDictionary<string, string?>? CreateMetadata(IEnumerable<KeyValuePair<string, string?>> metadata)
{
Dictionary<string, string?>? dictionary = null;
foreach (var pair in metadata ?? Array.Empty<KeyValuePair<string, string?>>())
{
if (string.IsNullOrWhiteSpace(pair.Key) || string.IsNullOrWhiteSpace(pair.Value))
{
continue;
}
dictionary ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
dictionary[pair.Key] = pair.Value;
}
return dictionary;
}
private void TryIngestRuntimeTrace(LanguageAnalyzerContext context)
{
if (context.AnalysisStore is not { } analysisStore)
{
return;
}
var tracePath = Path.Combine(context.RootPath, "deno-runtime.ndjson");
if (!File.Exists(tracePath))
{
return;
}
byte[] content;
try
{
content = File.ReadAllBytes(tracePath);
}
catch (IOException)
{
return;
}
var (metadata, hash) = DenoRuntimeTraceProbe.Analyze(content);
var runtimeMeta = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["deno.runtime.hash"] = hash,
["deno.runtime.event_count"] = metadata.EventCount.ToString(CultureInfo.InvariantCulture),
["deno.runtime.permission_uses"] = metadata.PermissionUses.ToString(CultureInfo.InvariantCulture),
["deno.runtime.module_loads"] = metadata.ModuleLoads.ToString(CultureInfo.InvariantCulture),
["deno.runtime.remote_origins"] = string.Join(',', metadata.RemoteOrigins),
["deno.runtime.permissions"] = string.Join(',', metadata.UniquePermissions),
["deno.runtime.npm_resolutions"] = metadata.NpmResolutions.ToString(CultureInfo.InvariantCulture),
["deno.runtime.wasm_loads"] = metadata.WasmLoads.ToString(CultureInfo.InvariantCulture),
["deno.runtime.dynamic_imports"] = metadata.DynamicImports.ToString(CultureInfo.InvariantCulture)
};
var payload = new AnalyzerObservationPayload(
analyzerId: Id,
kind: "deno.runtime.v1",
mediaType: "application/x-ndjson",
content: content,
metadata: runtimeMeta,
view: "runtime");
analysisStore.Set(ScanAnalysisKeys.DenoRuntimePayload, payload);
// Backward compatibility with early runtime experiments that used a string key.
analysisStore.Set("deno.runtime", payload);
// Also emit policy signals into AnalysisStore for downstream consumption.
var signals = DenoPolicySignalEmitter.FromTrace(hash, metadata);
foreach (var signal in signals)
{
analysisStore.Set(signal.Key, signal.Value);
}
}
}