feat: Add MongoIdempotencyStoreOptions for MongoDB configuration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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:
@@ -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);
|
||||
|
||||
@@ -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>());
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -8,4 +8,5 @@ internal sealed record ComposerPackage(
|
||||
string? SourceType,
|
||||
string? SourceReference,
|
||||
string? DistSha256,
|
||||
string? DistUrl);
|
||||
string? DistUrl,
|
||||
ComposerAutoloadData Autoload);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// offline-friendly deno entrypoint for shim smoke test
|
||||
console.log("shim-fixture-start");
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user