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);