up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 09:07:40 +02:00
parent 150b3730ef
commit e6119cbe91
59 changed files with 1827 additions and 204 deletions

View File

@@ -1,3 +1,4 @@
using System.IO.Compression;
using System.Text.Json;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
@@ -58,6 +59,7 @@ internal static class NodePackageCollector
}
TraverseTarballs(context, lockData, workspaceIndex, packages, visited, yarnPnpPresent, cancellationToken);
TraverseYarnPnpCache(context, packages, visited, yarnPnpPresent, cancellationToken);
AppendDeclaredPackages(packages, lockData);
@@ -349,6 +351,110 @@ internal static class NodePackageCollector
}
}
private static void TraverseYarnPnpCache(
LanguageAnalyzerContext context,
List<NodePackage> packages,
HashSet<string> visited,
bool yarnPnpPresent,
CancellationToken cancellationToken)
{
if (!yarnPnpPresent)
{
return;
}
var cacheDirectory = Path.Combine(context.RootPath, ".yarn", "cache");
if (!Directory.Exists(cacheDirectory))
{
return;
}
var enumerationOptions = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
AttributesToSkip = FileAttributes.ReparsePoint | FileAttributes.Device
};
foreach (var zipPath in Directory.EnumerateFiles(cacheDirectory, "*.zip", enumerationOptions))
{
cancellationToken.ThrowIfCancellationRequested();
TryProcessZipball(context, zipPath, packages, visited, yarnPnpPresent, cancellationToken);
}
}
private static void TryProcessZipball(
LanguageAnalyzerContext context,
string zipPath,
List<NodePackage> packages,
HashSet<string> visited,
bool yarnPnpPresent,
CancellationToken cancellationToken)
{
try
{
using var archive = ZipFile.OpenRead(zipPath);
var packageEntry = archive.Entries
.FirstOrDefault(entry => entry.FullName.EndsWith("package.json", StringComparison.OrdinalIgnoreCase));
if (packageEntry is null || packageEntry.Length == 0)
{
return;
}
using var entryStream = packageEntry.Open();
using var buffer = new MemoryStream();
entryStream.CopyTo(buffer);
buffer.Position = 0;
var sha256 = SHA256.HashData(buffer.ToArray());
var sha256Hex = Convert.ToHexString(sha256).ToLowerInvariant();
buffer.Position = 0;
using var document = JsonDocument.Parse(buffer);
var root = document.RootElement;
var relativeDirectory = NormalizeRelativeDirectoryZip(context, zipPath);
var locator = BuildZipLocator(context, zipPath, packageEntry.FullName);
var usedByEntrypoint = context.UsageHints.IsPathUsed(zipPath);
var package = TryCreatePackageFromJson(
context,
root,
relativeDirectory,
locator,
usedByEntrypoint,
cancellationToken,
lockData: null,
workspaceIndex: null,
packageJsonPath: null,
packageSha256: sha256Hex,
yarnPnpPresent: yarnPnpPresent);
if (package is null)
{
return;
}
if (visited.Add($"zip::{locator}"))
{
packages.Add(package);
}
}
catch (IOException)
{
// ignore unreadable zipballs
}
catch (InvalidDataException)
{
// ignore invalid zip payloads
}
catch (JsonException)
{
// ignore malformed package definitions in zips
}
}
private static void AppendDeclaredPackages(List<NodePackage> packages, NodeLockData lockData)
{
if (lockData.DeclaredPackages.Count == 0)
@@ -572,6 +678,17 @@ internal static class NodePackageCollector
return $"{normalizedArchive}!{normalizedEntry}";
}
private static string BuildZipLocator(LanguageAnalyzerContext context, string zipPath, string entryName)
{
var relative = context.GetRelativePath(zipPath);
var normalizedArchive = string.IsNullOrWhiteSpace(relative) || relative == "."
? Path.GetFileName(zipPath)
: relative.Replace(Path.DirectorySeparatorChar, '/');
var normalizedEntry = entryName.Replace('\\', '/');
return $"{normalizedArchive}!{normalizedEntry}";
}
private static string NormalizeRelativeDirectoryTar(LanguageAnalyzerContext context, string tgzPath)
{
var relative = context.GetRelativePath(Path.GetDirectoryName(tgzPath)!);
@@ -583,6 +700,17 @@ internal static class NodePackageCollector
return relative.Replace(Path.DirectorySeparatorChar, '/');
}
private static string NormalizeRelativeDirectoryZip(LanguageAnalyzerContext context, string zipPath)
{
var relative = context.GetRelativePath(Path.GetDirectoryName(zipPath)!);
if (string.IsNullOrEmpty(relative) || relative == ".")
{
return "zip";
}
return relative.Replace(Path.DirectorySeparatorChar, '/');
}
private static bool ShouldSkipDirectory(string name)
{
if (name.Length == 0)

View File

@@ -1,4 +1,25 @@
[
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/cached-lib@1.0.0",
"purl": "pkg:npm/cached-lib@1.0.0",
"name": "cached-lib",
"version": "1.0.0",
"type": "npm",
"usedByEntrypoint": false,
"metadata": {
"path": ".yarn/cache",
"yarnPnp": "true"
},
"evidence": [
{
"kind": "file",
"source": "package.json",
"locator": ".yarn/cache/cached-lib-1.0.0.zip!package/package.json",
"sha256": "b13d2a5d313d5929280c14af2086e23ca8f0d60761085c0ad44982ec307c92e3"
}
]
},
{
"analyzerId": "node",
"componentKey": "purl::pkg:npm/yarn-pnp-demo@1.0.0",

View File

@@ -0,0 +1,28 @@
# Deno Runtime Trace Collection (DENO-26-010)
This shows how to collect Deno runtime traces with the existing analyzer runtime runner (no code changes required).
## Prereqs
- `deno` binary available locally (cached; no network fetch).
- Set `STELLA_DENO_ENTRYPOINT` to the entry file of the Deno app (relative to repo root or absolute).
- Optional: set `STELLA_DENO_TRACE_ARGS` for extra `deno run` args (e.g., `-A`).
## How to run via analyzer/worker
1. Ensure the scanner job sets the environment variable before invoking analyzers:
- `STELLA_DENO_ENTRYPOINT=app.ts`
2. Run the scanner (worker or CLI) as usual. The Deno analyzer will:
- Generate and write the runtime shim next to the entrypoint.
- Execute `deno run` with the shim to produce `deno-runtime.ndjson`.
- Parse the NDJSON into AnalysisStore under `ScanAnalysisKeys.DenoRuntimePayload` and emit policy signals.
## Offline/airgap notes
- No outbound network calls; all modules must be local/cached.
- Paths are hashed deterministically; timestamps are UTC.
- If `deno` is missing or entrypoint unset, runtime capture is skipped (no failure).
## CLI shortcut
You can invoke the analyzer tests as a smoke check:
```bash
dotnet test src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Deno.Tests/StellaOps.Scanner.Analyzers.Lang.Deno.Tests.csproj -c Release
```
This ensures the runtime runner and parser remain healthy.