Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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
193 lines
7.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|