feat: Add MongoIdempotencyStoreOptions for MongoDB configuration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

feat: Implement BsonJsonConverter for converting BsonDocument and BsonArray to JSON

fix: Update project file to include MongoDB.Bson package

test: Add GraphOverlayExporterTests to validate NDJSON export functionality

refactor: Refactor Program.cs in Attestation Tool for improved argument parsing and error handling

docs: Update README for stella-forensic-verify with usage instructions and exit codes

feat: Enhance HmacVerifier with clock skew and not-after checks

feat: Add MerkleRootVerifier and ChainOfCustodyVerifier for additional verification methods

fix: Update DenoRuntimeShim to correctly handle file paths

feat: Introduce ComposerAutoloadData and related parsing in ComposerLockReader

test: Add tests for Deno runtime execution and verification

test: Enhance PHP package tests to include autoload data verification

test: Add unit tests for HmacVerifier and verification logic
This commit is contained in:
StellaOps Bot
2025-11-22 16:42:56 +02:00
parent 967ae0ab16
commit dc7c75b496
85 changed files with 2272 additions and 917 deletions

View File

@@ -190,7 +190,7 @@ function relativePath(path: string): string {
candidate = candidate.slice("file://".length);
}
if (!candidate.startsWith("/") && !/^([A-Za-z]:\\\\|[A-Za-z]:\\/)/.test(candidate)) {
if (!candidate.startsWith("/") && !/^([A-Za-z]:\\|[A-Za-z]:\/)/.test(candidate)) {
candidate = `${cwd}/${candidate}`;
}
@@ -209,7 +209,7 @@ function toFileUrl(path: string): URL {
return new URL(normalized);
}
const absolute = normalized.startsWith("/") || /^([A-Za-z]:\\\\|[A-Za-z]:\\/)/.test(normalized)
const absolute = normalized.startsWith("/") || /^([A-Za-z]:\\|[A-Za-z]:\/)/.test(normalized)
? normalized
: `${cwd}/${normalized}`;
@@ -430,10 +430,8 @@ function flush() {
return at.localeCompare(bt);
});
const data = sorted.map((e) => JSON.stringify(e)).join("
");
Deno.writeTextFileSync("deno-runtime.ndjson", data ? `${data}
` : "");
const data = sorted.map((e) => JSON.stringify(e)).join("\\n");
Deno.writeTextFileSync("deno-runtime.ndjson", data ? `${data}\\n` : "");
} catch (err) {
// last-resort logging; avoid throwing
console.error("deno-runtime shim failed to write trace", err);

View File

@@ -0,0 +1,24 @@
namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal;
internal sealed class ComposerAutoloadData
{
public ComposerAutoloadData(
IReadOnlyList<string> psr4,
IReadOnlyList<string> classmap,
IReadOnlyList<string> files)
{
Psr4 = psr4 ?? Array.Empty<string>();
Classmap = classmap ?? Array.Empty<string>();
Files = files ?? Array.Empty<string>();
}
public IReadOnlyList<string> Psr4 { get; }
public IReadOnlyList<string> Classmap { get; }
public IReadOnlyList<string> Files { get; }
public bool IsEmpty => Psr4.Count == 0 && Classmap.Count == 0 && Files.Count == 0;
public static ComposerAutoloadData Empty { get; } = new(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
}

View File

@@ -22,18 +22,18 @@ internal static class ComposerLockReader
var contentHash = TryGetString(root, "content-hash");
var pluginApiVersion = TryGetString(root, "plugin-api-version");
var packages = ParsePackages(root, propertyName: "packages", isDev: false);
var devPackages = ParsePackages(root, propertyName: "packages-dev", isDev: true);
var lockSha = await ComputeSha256Async(lockPath, cancellationToken).ConfigureAwait(false);
var packages = ParsePackages(root, propertyName: "packages", isDev: false);
var devPackages = ParsePackages(root, propertyName: "packages-dev", isDev: true);
var lockSha = await ComputeSha256Async(lockPath, cancellationToken).ConfigureAwait(false);
return new ComposerLockData(
lockPath,
contentHash,
pluginApiVersion,
packages,
devPackages,
lockSha);
}
return new ComposerLockData(
lockPath,
contentHash,
pluginApiVersion,
packages,
devPackages,
lockSha);
}
private static IReadOnlyList<ComposerPackage> ParsePackages(JsonElement root, string propertyName, bool isDev)
{
@@ -54,6 +54,7 @@ internal static class ComposerLockReader
var type = TryGetString(packageElement, "type");
var (sourceType, sourceReference) = ParseSource(packageElement);
var (distSha, distUrl) = ParseDist(packageElement);
var autoload = ParseAutoload(packageElement);
packages.Add(new ComposerPackage(
name,
@@ -63,7 +64,8 @@ internal static class ComposerLockReader
sourceType,
sourceReference,
distSha,
distUrl));
distUrl,
autoload));
}
return packages;
@@ -93,6 +95,67 @@ internal static class ComposerLockReader
return (distSha, distUrl);
}
private static ComposerAutoloadData ParseAutoload(JsonElement packageElement)
{
if (!packageElement.TryGetProperty("autoload", out var autoloadElement) || autoloadElement.ValueKind != JsonValueKind.Object)
{
return ComposerAutoloadData.Empty;
}
var psr4 = new List<string>();
if (autoloadElement.TryGetProperty("psr-4", out var psr4Element) && psr4Element.ValueKind == JsonValueKind.Object)
{
foreach (var ns in psr4Element.EnumerateObject())
{
var key = ns.Name;
if (ns.Value.ValueKind == JsonValueKind.String)
{
psr4.Add($"{key}->{NormalizePath(ns.Value.GetString())}");
}
else if (ns.Value.ValueKind == JsonValueKind.Array)
{
foreach (var pathElement in ns.Value.EnumerateArray())
{
if (pathElement.ValueKind == JsonValueKind.String)
{
psr4.Add($"{key}->{NormalizePath(pathElement.GetString())}");
}
}
}
}
}
var classmap = new List<string>();
if (autoloadElement.TryGetProperty("classmap", out var classmapElement) && classmapElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in classmapElement.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String)
{
classmap.Add(NormalizePath(item.GetString()));
}
}
}
var files = new List<string>();
if (autoloadElement.TryGetProperty("files", out var filesElement) && filesElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in filesElement.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String)
{
files.Add(NormalizePath(item.GetString()));
}
}
}
psr4.Sort(StringComparer.Ordinal);
classmap.Sort(StringComparer.Ordinal);
files.Sort(StringComparer.Ordinal);
return new ComposerAutoloadData(psr4, classmap, files);
}
private static string? TryGetString(JsonElement element, string propertyName)
=> TryGetString(element, propertyName, out var value) ? value : null;
@@ -113,6 +176,9 @@ internal static class ComposerLockReader
return false;
}
private static string NormalizePath(string? path)
=> string.IsNullOrWhiteSpace(path) ? string.Empty : path.Replace('\\', '/');
private static async ValueTask<string> ComputeSha256Async(string path, CancellationToken cancellationToken)
{
await using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);

View File

@@ -8,4 +8,5 @@ internal sealed record ComposerPackage(
string? SourceType,
string? SourceReference,
string? DistSha256,
string? DistUrl);
string? DistUrl,
ComposerAutoloadData Autoload);

View File

@@ -38,6 +38,30 @@ internal sealed class PhpPackage
yield return new KeyValuePair<string, string?>("composer.source.ref", _package.SourceReference);
}
if (!_package.Autoload.IsEmpty)
{
if (_package.Autoload.Psr4.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"composer.autoload.psr4",
string.Join(';', _package.Autoload.Psr4));
}
if (_package.Autoload.Classmap.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"composer.autoload.classmap",
string.Join(';', _package.Autoload.Classmap));
}
if (_package.Autoload.Files.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"composer.autoload.files",
string.Join(';', _package.Autoload.Files));
}
}
if (!string.IsNullOrWhiteSpace(_package.DistSha256))
{
yield return new KeyValuePair<string, string?>("composer.dist.sha256", _package.DistSha256);

View File

@@ -73,6 +73,44 @@ public sealed class DenoRuntimeTraceRunnerTests
}
}
[Fact]
public async Task ExecutesShimAndWritesRuntime_WhenDenoPresent()
{
var binary = DenoBinaryLocator.Find();
if (string.IsNullOrWhiteSpace(binary))
{
return; // gracefully skip when deno is unavailable in the environment
}
var root = TestPaths.CreateTemporaryDirectory();
try
{
var entry = Path.Combine(root, "main.ts");
var fixture = Path.Combine(TestPaths.GetProjectRoot(), "TestFixtures/deno-runtime/simple/main.ts");
File.Copy(fixture, entry);
using var entryEnv = new EnvironmentVariableScope("STELLA_DENO_ENTRYPOINT", "main.ts");
using var binaryEnv = new EnvironmentVariableScope("STELLA_DENO_BINARY", binary);
using var denoDirEnv = new EnvironmentVariableScope("DENO_DIR", Path.Combine(root, ".deno-cache"));
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var result = await DenoRuntimeTraceRunner.TryExecuteAsync(context, logger: null, CancellationToken.None);
Assert.True(result);
var runtimePath = Path.Combine(root, "deno-runtime.ndjson");
Assert.True(File.Exists(runtimePath));
var content = await File.ReadAllTextAsync(runtimePath);
Assert.Contains("deno.runtime.start", content);
Assert.Contains("deno.module.load", content);
}
finally
{
TestPaths.SafeDelete(root);
}
}
private sealed class EnvironmentVariableScope : IDisposable
{
private readonly string _name;

View File

@@ -0,0 +1,2 @@
// offline-friendly deno entrypoint for shim smoke test
console.log("shim-fixture-start");

View File

@@ -0,0 +1,47 @@
using System.Runtime.InteropServices;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestUtilities;
internal static class DenoBinaryLocator
{
public static string? Find()
{
var candidates = new List<string>();
var envBinary = Environment.GetEnvironmentVariable("STELLA_DENO_BINARY");
if (!string.IsNullOrWhiteSpace(envBinary))
{
candidates.Add(envBinary);
}
var path = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ';' : ':';
var exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "deno.exe" : "deno";
foreach (var segment in path.Split(separator, StringSplitOptions.RemoveEmptyEntries))
{
candidates.Add(Path.Combine(segment, exeName));
}
foreach (var candidate in candidates)
{
if (string.IsNullOrWhiteSpace(candidate))
{
continue;
}
try
{
if (File.Exists(candidate))
{
return candidate;
}
}
catch
{
// ignore malformed paths
}
}
return null;
}
}

View File

@@ -15,6 +15,18 @@
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0123456789abcdef0123456789abcdef01234567",
"shasum": "6f1b4c0908a5c2fdc3fbc0351d1a8f5f"
},
"autoload": {
"psr-4": {
"Illuminate\\": "src/Illuminate",
"Laravel\\": ["src/Laravel", "src/Laravel/Support"]
},
"classmap": [
"src/Illuminate/Support/helpers.php"
],
"files": [
"src/Illuminate/Foundation/helpers.php"
]
}
}
],
@@ -27,6 +39,14 @@
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9c9d4e1c8b62f9142fe995c3d76343d6330f0e36"
},
"autoload": {
"psr-4": {
"PHPUnit\\Framework\\": "src/Framework"
},
"files": [
"src/Framework/Assert/Functions.php"
]
}
}
]

View File

@@ -8,6 +8,9 @@
"type": "composer",
"usedByEntrypoint": false,
"metadata": {
"composer.autoload.classmap": "src/Illuminate/Support/helpers.php",
"composer.autoload.files": "src/Illuminate/Foundation/helpers.php",
"composer.autoload.psr4": "Illuminate\\->src/Illuminate;Laravel\\->src/Laravel;Laravel\\->src/Laravel/Support",
"composer.content_hash": "e01f9b7d7f4b23a6d1ad3b8e91c1c4ae",
"composer.dev": "false",
"composer.dist.sha256": "6f1b4c0908a5c2fdc3fbc0351d1a8f5f",
@@ -24,7 +27,7 @@
"source": "composer.lock",
"locator": "composer.lock",
"value": "laravel/framework@10.48.7",
"sha256": "469f987fef544c06365b59539ec5e48d5356011ff829b36b96ec1336be2de9d1"
"sha256": "885d825c2fcde1ce56a468ef193ef63a815d357f11465e29f382d9777d9a5706"
}
]
},
@@ -37,6 +40,8 @@
"type": "composer",
"usedByEntrypoint": false,
"metadata": {
"composer.autoload.files": "src/Framework/Assert/Functions.php",
"composer.autoload.psr4": "PHPUnit\\Framework\\->src/Framework",
"composer.content_hash": "e01f9b7d7f4b23a6d1ad3b8e91c1c4ae",
"composer.dev": "true",
"composer.plugin_api_version": "2.6.0",
@@ -51,7 +56,7 @@
"source": "composer.lock",
"locator": "composer.lock",
"value": "phpunit/phpunit@10.5.5",
"sha256": "469f987fef544c06365b59539ec5e48d5356011ff829b36b96ec1336be2de9d1"
"sha256": "885d825c2fcde1ce56a468ef193ef63a815d357f11465e29f382d9777d9a5706"
}
]
}