This commit is contained in:
StellaOps Bot
2025-12-09 00:20:52 +02:00
parent 3d01bf9edc
commit bc0762e97d
261 changed files with 14033 additions and 4427 deletions

View File

@@ -40,7 +40,8 @@
{
"kind": "file",
"source": "package.json",
"locator": "package.json"
"locator": "package.json",
"sha256": "65e86ba14f0beebc4573039ac34a58f6dfa0133aa4a9e7f2dcdbb36a4e5c2814"
}
]
}

View File

@@ -594,11 +594,7 @@ public sealed class NodePackageCollectorTests
}
private static string InvokeBuildDeclarationKey(string name, string? version)
{
var method = typeof(NodePackageCollector).GetMethod("BuildDeclarationKey",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
return (string?)method?.Invoke(null, [name, version]) ?? string.Empty;
}
=> NodeDeclarationKeyBuilder.Build(name, version);
#endregion
}

View File

@@ -26,9 +26,9 @@
"projectUrl": "Documentation, https://example.com/layered/docs",
"provenance": "dist-info",
"record.hashMismatches": "0",
"record.hashedEntries": "8",
"record.hashedEntries": "7",
"record.ioErrors": "0",
"record.missingFiles": "0",
"record.missingFiles": "1",
"record.totalEntries": "9",
"requiresDist": "requests",
"requiresPython": "\u003E=3.9",
@@ -44,6 +44,12 @@
"wheel.version": "1.0"
},
"evidence": [
{
"kind": "derived",
"source": "RECORD",
"locator": "layer1/usr/bin/layered-cli",
"value": "missing"
},
{
"kind": "file",
"source": "INSTALLER",

View File

@@ -30,6 +30,8 @@
"record.unsupportedAlgorithms": "md5",
"requiresDist": "click",
"requiresPython": "\u003E=3.8",
"runtime.libPaths.count": "1",
"runtime.versions": "3.11",
"summary": "Cache test package for hashed RECORD coverage",
"version": "1.2.3",
"wheel.generator": "pip 24.0",

View File

@@ -31,6 +31,8 @@
"record.totalEntries": "10",
"requiresDist": "requests (\u003E=2.0)",
"requiresPython": "\u003E=3.9",
"runtime.libPaths.count": "1",
"runtime.versions": "3.11",
"sourceCommit": "abc123def",
"sourceSubdirectory": "src/simple",
"sourceUrl": "https://example.com/simple-1.0.0.tar.gz",

View File

@@ -360,7 +360,8 @@ public sealed class PythonLanguageAnalyzerTests
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
var expectedPath = Path.Combine("lib", "python3.11", "site-packages", "egg_info_pkg-1.2.3.egg-info");
var expectedPath = Path.Combine("lib", "python3.11", "site-packages", "egg_info_pkg-1.2.3.egg-info")
.Replace('\\', '/');
Assert.True(ComponentHasMetadata(root, "egg-info-pkg", "provenance", "egg-info"));
Assert.True(ComponentHasMetadata(root, "egg-info-pkg", "record.totalEntries", "4"));

View File

@@ -77,16 +77,22 @@ public sealed class PythonVirtualFileSystemTests
using (var archive = ZipFile.Open(wheelPath, ZipArchiveMode.Create))
{
var entry = archive.CreateEntry("mypackage/__init__.py");
using var writer = new StreamWriter(entry.Open());
writer.Write("# Package init");
using (var writer = new StreamWriter(entry.Open()))
{
writer.Write("# Package init");
}
entry = archive.CreateEntry("mypackage/core.py");
using var writer2 = new StreamWriter(entry.Open());
writer2.Write("def main(): pass");
using (var writer2 = new StreamWriter(entry.Open()))
{
writer2.Write("def main(): pass");
}
entry = archive.CreateEntry("mypackage-1.0.0.dist-info/METADATA");
using var writer3 = new StreamWriter(entry.Open());
writer3.Write("Name: mypackage\nVersion: 1.0.0");
using (var writer3 = new StreamWriter(entry.Open()))
{
writer3.Write("Name: mypackage\nVersion: 1.0.0");
}
}
var vfs = PythonVirtualFileSystem.CreateBuilder()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
@@ -7,72 +7,72 @@ using System.Threading.Tasks;
using StellaOps.Scanner.Analyzers.Lang.DotNet;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Tests.DotNet;
public sealed class DotNetLanguageAnalyzerTests
{
[Fact]
public async Task SimpleFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "simple");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task SignedFixtureCapturesAssemblyMetadataAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "signed");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var inspector = new StubAuthenticodeInspector();
var services = new SingleServiceProvider(inspector);
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints: null,
services: services);
}
[Fact]
public async Task SelfContainedFixtureHandlesNativeAssetsAndUsageAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "lib", "net10.0", "StellaOps.Toolkit.dll"),
Path.Combine(fixturePath, "runtimes", "linux-x64", "native", "libstellaopsnative.so")
});
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
namespace StellaOps.Scanner.Analyzers.Lang.Tests.DotNet;
public sealed class DotNetLanguageAnalyzerTests
{
[Fact]
public async Task SimpleFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "simple");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken);
}
[Fact]
public async Task SignedFixtureCapturesAssemblyMetadataAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "signed");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var inspector = new StubAuthenticodeInspector();
var services = new SingleServiceProvider(inspector);
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints: null,
services: services);
}
[Fact]
public async Task SelfContainedFixtureHandlesNativeAssetsAndUsageAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "lib", "net10.0", "StellaOps.Toolkit.dll"),
Path.Combine(fixturePath, "runtimes", "linux-x64", "native", "libstellaopsnative.so")
});
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
@@ -104,6 +104,68 @@ public sealed class DotNetLanguageAnalyzerTests
}
}
[Fact]
public async Task ConfigEnablesIlMetadataEdgesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var sourceFixture = TestPaths.ResolveFixture("lang", "dotnet", "simple");
var tempRoot = TestPaths.CreateTemporaryDirectory();
try
{
CopyDirectory(sourceFixture, tempRoot);
File.WriteAllText(
Path.Combine(tempRoot, "dotnet-il.config.json"),
"""
{
"emitDependencyEdges": true,
"includeEntrypoints": true,
"runtimeEvidencePath": "runtime-evidence.ndjson",
"runtimeEvidenceConfidence": "medium"
}
""");
File.WriteAllLines(
Path.Combine(tempRoot, "runtime-evidence.ndjson"),
new[]
{
"{\"package\":\"stellaops.toolkit\",\"target\":\"native-lib\",\"reason\":\"runtime-load\",\"confidence\":\"medium\",\"source\":\"trace\"}"
});
var analyzers = new ILanguageAnalyzer[]
{
new DotNetLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
tempRoot,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var packages = document.RootElement.EnumerateArray().ToArray();
var toolkit = packages.First(element => element.GetProperty("name").GetString() == "StellaOps.Toolkit");
var logging = packages.First(element => element.GetProperty("name").GetString() == "Microsoft.Extensions.Logging");
var toolkitMetadata = toolkit.GetProperty("metadata");
Assert.Equal("microsoft.extensions.logging", toolkitMetadata.GetProperty("edge[0].target").GetString());
Assert.Equal("declared-dependency", toolkitMetadata.GetProperty("edge[0].reason").GetString());
Assert.Equal("native-lib", toolkitMetadata.GetProperty("edge.runtime[0].target").GetString());
Assert.Equal("runtime-load", toolkitMetadata.GetProperty("edge.runtime[0].reason").GetString());
var entrypointId = toolkitMetadata.GetProperty("entrypoint[0].id").GetString();
Assert.NotNull(entrypointId);
Assert.StartsWith("Sample.App:", entrypointId, StringComparison.Ordinal);
Assert.True(toolkitMetadata.TryGetProperty("entrypoint[0].tfm[0]", out _));
Assert.True(logging.GetProperty("metadata").TryGetProperty("entrypoint[0].rid[0]", out _));
}
finally
{
TestPaths.SafeDelete(tempRoot);
}
}
[Fact]
public async Task MultiFixtureMergesRuntimeMetadataAsync()
{
@@ -151,29 +213,49 @@ public sealed class DotNetLanguageAnalyzerTests
Assert.Contains("osx-arm64", ridValues);
Assert.Contains("win-arm64", ridValues);
}
private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector
{
public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken)
=> new DotNetAuthenticodeMetadata(
Subject: "CN=StellaOps Test Signing",
Issuer: "CN=StellaOps Root",
NotBefore: new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
NotAfter: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero),
Thumbprint: "AA11BB22CC33DD44EE55FF66GG77HH88II99JJ00",
SerialNumber: "0123456789ABCDEF");
}
private sealed class SingleServiceProvider : IServiceProvider
{
private readonly object _service;
public SingleServiceProvider(object service)
{
_service = service;
}
public object? GetService(Type serviceType)
=> serviceType == typeof(IDotNetAuthenticodeInspector) ? _service : null;
}
}
private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector
{
public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken)
=> new DotNetAuthenticodeMetadata(
Subject: "CN=StellaOps Test Signing",
Issuer: "CN=StellaOps Root",
NotBefore: new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
NotAfter: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero),
Thumbprint: "AA11BB22CC33DD44EE55FF66GG77HH88II99JJ00",
SerialNumber: "0123456789ABCDEF");
}
private sealed class SingleServiceProvider : IServiceProvider
{
private readonly object _service;
public SingleServiceProvider(object service)
{
_service = service;
}
public object? GetService(Type serviceType)
=> serviceType == typeof(IDotNetAuthenticodeInspector) ? _service : null;
}
private static void CopyDirectory(string sourceDir, string destinationDir)
{
if (!Directory.Exists(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
foreach (var file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly))
{
var targetPath = Path.Combine(destinationDir, Path.GetFileName(file));
File.Copy(file, targetPath, overwrite: true);
}
foreach (var directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.TopDirectoryOnly))
{
var targetDirectory = Path.Combine(destinationDir, Path.GetFileName(directory));
CopyDirectory(directory, targetDirectory);
}
}
}

View File

@@ -0,0 +1,88 @@
using FluentAssertions;
using StellaOps.Scanner.Analyzers.Native.Observations;
using StellaOps.Scanner.Analyzers.Native.Reachability;
using Xunit;
namespace StellaOps.Scanner.Analyzers.Native.Tests.Reachability;
public class NativeReachabilityGraphBuilderTests
{
[Fact]
public void Build_ConstructsDeterministicGraphWithSyntheticRoots()
{
// Arrange
var document = new NativeObservationDocument
{
Binary = new NativeObservationBinary
{
Path = "/usr/bin/test",
Format = "elf",
BuildId = "abcd1234",
Architecture = "x86_64",
Is64Bit = true,
},
Entrypoints =
[
new NativeObservationEntrypoint
{
Type = "main",
Symbol = "_start",
}
],
DeclaredEdges =
[
new NativeObservationDeclaredEdge
{
Target = "libc.so.6",
Reason = "elf-dtneeded",
}
],
HeuristicEdges =
[
new NativeObservationHeuristicEdge
{
Target = "libplugin.so",
Reason = "string-dlopen",
Confidence = "medium",
}
],
Environment = new NativeObservationEnvironment()
};
// Act
var graph = NativeReachabilityGraphBuilder.Build(document, layerDigest: "sha256:layer1");
// Assert
graph.LayerDigest.Should().Be("sha256:layer1");
graph.BuildId.Should().Be("abcd1234");
graph.Nodes.Should().ContainSingle(n => n.Kind == "root");
graph.Nodes.Should().ContainSingle(n => n.Kind == "binary");
graph.Nodes.Should().Contain(n => n.Name == "libc.so.6");
graph.Edges.Should().Contain(e => e.Reason == "entrypoint");
graph.Edges.Should().Contain(e => e.Reason == "elf-dtneeded");
}
[Fact]
public void ToBundle_WrapsGraphWithPayloadType()
{
// Arrange
var document = new NativeObservationDocument
{
Binary = new NativeObservationBinary
{
Path = "/usr/bin/test",
Format = "elf",
Sha256 = "aa",
Is64Bit = true,
},
Environment = new NativeObservationEnvironment()
};
// Act
var bundle = NativeReachabilityGraphBuilder.ToBundle(document);
// Assert
bundle.PayloadType.Should().Be("stellaops.native.graph@1");
bundle.Graph.Nodes.Should().NotBeEmpty();
}
}