feat: Update analyzer fixtures and metadata for improved license handling and provenance tracking
- Added license expressions and provenance fields to expected JSON outputs for .NET and Rust analyzers. - Introduced new .nuspec files for StellaOps.Runtime.SelfContained and StellaOps.Toolkit packages, including license information. - Created LICENSE.txt files for both toolkit packages with clear licensing terms. - Updated expected JSON for signed and simple analyzers to include license information and provenance. - Enhanced the SPRINTS_LANG_IMPLEMENTATION_PLAN.md with detailed progress and future sprint outlines, ensuring clarity on deliverables and acceptance metrics.
This commit is contained in:
@@ -118,7 +118,7 @@ Generated from SPRINTS.md and module TASKS.md files on 2025-10-19. Waves cluster
|
||||
- Team Notify Worker Guild: read EXECPLAN.md Wave 4 and SPRINTS.md rows for `src/StellaOps.Notify.Worker/TASKS.md`. Focus on NOTIFY-WORKER-15-204 (TODO). Confirm prerequisites (internal: NOTIFY-WORKER-15-203 (Wave 3)) before starting and report status in module TASKS.md.
|
||||
- Team Policy Guild, Scanner WebService Guild: read EXECPLAN.md Wave 4 and SPRINTS.md rows for `src/StellaOps.Policy/TASKS.md`. Focus on POLICY-RUNTIME-17-201 (TODO). Confirm prerequisites (internal: ZASTAVA-OBS-17-005 (Wave 3)) before starting and report status in module TASKS.md.
|
||||
- Team Scheduler Worker Guild: read EXECPLAN.md Wave 4 and SPRINTS.md rows for `src/StellaOps.Scheduler.Worker/TASKS.md`. Focus on SCHED-WORKER-16-204 (TODO). Confirm prerequisites (internal: SCHED-WORKER-16-203 (Wave 3)) before starting and report status in module TASKS.md.
|
||||
- Team TBD: read EXECPLAN.md Wave 4 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-307D (TODO), SCANNER-ANALYZERS-LANG-10-307G (TODO), SCANNER-ANALYZERS-LANG-10-307P (TODO), SCANNER-ANALYZERS-LANG-10-307R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-303C (Wave 3), SCANNER-ANALYZERS-LANG-10-304C (Wave 3), SCANNER-ANALYZERS-LANG-10-305C (Wave 3), SCANNER-ANALYZERS-LANG-10-306C (Wave 3)) before starting and report status in module TASKS.md.
|
||||
- Team TBD: read EXECPLAN.md Wave 4 and SPRINTS.md rows for `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Python/TASKS.md`, `src/StellaOps.Scanner.Analyzers.Lang.Rust/TASKS.md`. Focus on SCANNER-ANALYZERS-LANG-10-307D (DONE 2025-10-22), SCANNER-ANALYZERS-LANG-10-307G (TODO), SCANNER-ANALYZERS-LANG-10-307P (TODO), SCANNER-ANALYZERS-LANG-10-307R (TODO). Confirm prerequisites (internal: SCANNER-ANALYZERS-LANG-10-303C (Wave 3), SCANNER-ANALYZERS-LANG-10-304C (Wave 3), SCANNER-ANALYZERS-LANG-10-305C (Wave 3), SCANNER-ANALYZERS-LANG-10-306C (Wave 3)) before starting and report status in module TASKS.md.
|
||||
|
||||
### Wave 5
|
||||
- Team Excititor Connectors – Stella: read EXECPLAN.md Wave 5 and SPRINTS.md rows for `src/StellaOps.Excititor.Connectors.StellaOpsMirror/TASKS.md`. Focus on EXCITITOR-CONN-STELLA-07-003 (TODO). Confirm prerequisites (internal: EXCITITOR-CONN-STELLA-07-002 (Wave 4)) before starting and report status in module TASKS.md.
|
||||
@@ -927,9 +927,9 @@ Generated from SPRINTS.md and module TASKS.md files on 2025-10-19. Waves cluster
|
||||
- **Sprint 10** · Backlog
|
||||
- Team: TBD
|
||||
- Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`
|
||||
1. [TODO] SCANNER-ANALYZERS-LANG-10-307D — Integrate shared helpers (license mapping, quiet provenance) and concurrency-safe caches.
|
||||
1. [DONE 2025-10-22] SCANNER-ANALYZERS-LANG-10-307D — Integrate shared helpers (license mapping, quiet provenance) and concurrency-safe caches.
|
||||
• Prereqs: SCANNER-ANALYZERS-LANG-10-305C (Wave 3)
|
||||
• Current: TODO
|
||||
• Current: DONE 2025-10-22
|
||||
- Path: `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`
|
||||
1. [TODO] SCANNER-ANALYZERS-LANG-10-307G — Wire shared helpers (license mapping, usage flags) and ensure concurrency-safe buffer reuse.
|
||||
• Prereqs: SCANNER-ANALYZERS-LANG-10-304C (Wave 3)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,332 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Xml;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
|
||||
|
||||
internal static class DotNetFileMetadataCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<string>> Sha256Cache = new();
|
||||
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<AssemblyName>> AssemblyCache = new();
|
||||
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<FileVersionInfo>> VersionCache = new();
|
||||
|
||||
public static bool TryGetSha256(string path, out string? sha256)
|
||||
=> TryGet(path, Sha256Cache, ComputeSha256, out sha256);
|
||||
|
||||
public static bool TryGetAssemblyName(string path, out AssemblyName? assemblyName)
|
||||
=> TryGet(path, AssemblyCache, TryReadAssemblyName, out assemblyName);
|
||||
|
||||
public static bool TryGetFileVersionInfo(string path, out FileVersionInfo? versionInfo)
|
||||
=> TryGet(path, VersionCache, TryReadFileVersionInfo, out versionInfo);
|
||||
|
||||
private static bool TryGet<T>(string path, ConcurrentDictionary<DotNetFileCacheKey, Optional<T>> cache, Func<string, T?> resolver, out T? value)
|
||||
where T : class
|
||||
{
|
||||
value = null;
|
||||
|
||||
DotNetFileCacheKey key;
|
||||
try
|
||||
{
|
||||
var info = new FileInfo(path);
|
||||
if (!info.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
key = new DotNetFileCacheKey(info.FullName, info.Length, info.LastWriteTimeUtc.Ticks);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var optional = cache.GetOrAdd(key, static (cacheKey, state) => CreateOptional(cacheKey.Path, state.resolver), (resolver, path));
|
||||
if (!optional.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = optional.Value;
|
||||
return value is not null;
|
||||
}
|
||||
|
||||
private static Optional<T> CreateOptional<T>(string path, Func<string, T?> resolver) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = resolver(path);
|
||||
return Optional<T>.From(value);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Optional<T>.None;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ComputeSha256(string path)
|
||||
{
|
||||
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var sha = System.Security.Cryptography.SHA256.Create();
|
||||
var hash = sha.ComputeHash(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static AssemblyName? TryReadAssemblyName(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return AssemblyName.GetAssemblyName(path);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FileVersionInfo? TryReadFileVersionInfo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FileVersionInfo.GetVersionInfo(path);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DotNetLicenseCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<DotNetLicenseInfo>> Licenses = new();
|
||||
|
||||
public static bool TryGetLicenseInfo(string nuspecPath, out DotNetLicenseInfo? info)
|
||||
{
|
||||
info = null;
|
||||
|
||||
DotNetFileCacheKey key;
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(nuspecPath);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
key = new DotNetFileCacheKey(fileInfo.FullName, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var optional = Licenses.GetOrAdd(key, static (cacheKey, path) => CreateOptional(path), nuspecPath);
|
||||
if (!optional.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
info = optional.Value;
|
||||
return info is not null;
|
||||
}
|
||||
|
||||
private static Optional<DotNetLicenseInfo> CreateOptional(string nuspecPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = Parse(nuspecPath);
|
||||
return Optional<DotNetLicenseInfo>.From(info);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Optional<DotNetLicenseInfo>.None;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Optional<DotNetLicenseInfo>.None;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
return Optional<DotNetLicenseInfo>.None;
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
return Optional<DotNetLicenseInfo>.None;
|
||||
}
|
||||
}
|
||||
|
||||
private static DotNetLicenseInfo? Parse(string path)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
using var reader = XmlReader.Create(stream, new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Ignore,
|
||||
IgnoreComments = true,
|
||||
IgnoreWhitespace = true,
|
||||
});
|
||||
|
||||
var expressions = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var files = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var urls = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(reader.LocalName, "license", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var type = reader.GetAttribute("type");
|
||||
var value = reader.ReadElementContentAsString()?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(type, "expression", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expressions.Add(value);
|
||||
}
|
||||
else if (string.Equals(type, "file", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
files.Add(NormalizeLicensePath(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
expressions.Add(value);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(reader.LocalName, "licenseUrl", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var value = reader.ReadElementContentAsString()?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
urls.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expressions.Count == 0 && files.Count == 0 && urls.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DotNetLicenseInfo(
|
||||
expressions.ToArray(),
|
||||
files.ToArray(),
|
||||
urls.ToArray());
|
||||
}
|
||||
|
||||
private static string NormalizeLicensePath(string value)
|
||||
=> value.Replace('\\', '/').Trim();
|
||||
}
|
||||
|
||||
internal sealed record DotNetLicenseInfo(
|
||||
IReadOnlyList<string> Expressions,
|
||||
IReadOnlyList<string> Files,
|
||||
IReadOnlyList<string> Urls);
|
||||
|
||||
internal readonly record struct DotNetFileCacheKey(string Path, long Length, long LastWriteTicks)
|
||||
{
|
||||
private readonly string _normalizedPath = OperatingSystem.IsWindows()
|
||||
? Path.ToLowerInvariant()
|
||||
: Path;
|
||||
|
||||
public bool Equals(DotNetFileCacheKey other)
|
||||
=> Length == other.Length
|
||||
&& LastWriteTicks == other.LastWriteTicks
|
||||
&& string.Equals(_normalizedPath, other._normalizedPath, StringComparison.Ordinal);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(_normalizedPath, Length, LastWriteTicks);
|
||||
}
|
||||
|
||||
internal readonly struct Optional<T> where T : class
|
||||
{
|
||||
private Optional(bool hasValue, T? value)
|
||||
{
|
||||
HasValue = hasValue;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public bool HasValue { get; }
|
||||
|
||||
public T? Value { get; }
|
||||
|
||||
public static Optional<T> From(T? value)
|
||||
=> value is null ? None : new Optional<T>(true, value);
|
||||
|
||||
public static Optional<T> None => default;
|
||||
}
|
||||
@@ -5,6 +5,6 @@
|
||||
| 1 | SCANNER-ANALYZERS-LANG-10-305A | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-307 | Parse `*.deps.json` + `runtimeconfig.json`, build RID graph, and normalize to `pkg:nuget` components. | RID graph deterministic; fixtures confirm consistent component ordering; fallback to `bin:{sha256}` documented. |
|
||||
| 2 | SCANNER-ANALYZERS-LANG-10-305B | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305A | Extract assembly metadata (strong name, file/product info) and optional Authenticode details when offline cert bundle provided. | Signing metadata captured for signed assemblies; offline trust store documented; hash validations deterministic. |
|
||||
| 3 | SCANNER-ANALYZERS-LANG-10-305C | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305B | Handle self-contained apps and native assets; merge with EntryTrace usage hints. | Self-contained fixtures map to components with RID flags; usage hints propagate; tests cover linux/win variants. |
|
||||
| 4 | SCANNER-ANALYZERS-LANG-10-307D | TODO | SCANNER-ANALYZERS-LANG-10-305C | Integrate shared helpers (license mapping, quiet provenance) and concurrency-safe caches. | Shared helpers reused; concurrency tests for parallel layer scans pass; no redundant allocations. |
|
||||
| 4 | SCANNER-ANALYZERS-LANG-10-307D | DONE (2025-10-22) | SCANNER-ANALYZERS-LANG-10-305C | Integrate shared helpers (license mapping, quiet provenance) and concurrency-safe caches. | Shared helpers reused; concurrency tests for parallel layer scans pass; no redundant allocations. |
|
||||
| 5 | SCANNER-ANALYZERS-LANG-10-308D | TODO | SCANNER-ANALYZERS-LANG-10-307D | Determinism fixtures + benchmark harness; compare to competitor scanners for accuracy/perf. | Fixtures in `Fixtures/lang/dotnet/`; determinism CI guard; benchmark demonstrates lower duplication + faster runtime. |
|
||||
| 6 | SCANNER-ANALYZERS-LANG-10-309D | TODO | SCANNER-ANALYZERS-LANG-10-308D | Package plug-in (manifest, DI registration) and update Offline Kit instructions. | Manifest copied to `plugins/scanner/analyzers/lang/`; Worker loads analyzer; Offline Kit doc updated. |
|
||||
|
||||
@@ -1,76 +1,77 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
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,
|
||||
@@ -79,28 +80,51 @@ public sealed class DotNetLanguageAnalyzerTests
|
||||
usageHints);
|
||||
}
|
||||
|
||||
private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector
|
||||
[Fact]
|
||||
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
|
||||
{
|
||||
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");
|
||||
}
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
|
||||
|
||||
private sealed class SingleServiceProvider : IServiceProvider
|
||||
{
|
||||
private readonly object _service;
|
||||
|
||||
public SingleServiceProvider(object service)
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
new DotNetLanguageAnalyzer()
|
||||
};
|
||||
|
||||
public object? GetService(Type serviceType)
|
||||
=> serviceType == typeof(IDotNetAuthenticodeInspector) ? _service : null;
|
||||
var workers = Math.Max(Environment.ProcessorCount, 4);
|
||||
var tasks = Enumerable.Range(0, workers)
|
||||
.Select(_ => LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
var first = results[0];
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Equal(first, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
"deps.rid[0]": "linux-x64",
|
||||
"deps.rid[1]": "win-x64",
|
||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"license.expression[0]": "Apache-2.0",
|
||||
"native[0].assetPath": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||
"native[0].path": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||
"native[0].rid[0]": "linux-x64",
|
||||
"native[0].sha256": "c22d4a6584a3bb8fad4d255d1ab9e5a80d553eec35ea8dfcc2dd750e8581d3cb",
|
||||
"native[0].sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0",
|
||||
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"native[1].assetPath": "runtimes/win-x64/native/stellaopsnative.dll",
|
||||
"native[1].path": "runtimes/win-x64/native/stellaopsnative.dll",
|
||||
"native[1].rid[0]": "win-x64",
|
||||
"native[1].sha256": "29cddd69702aedc715050304bec85aad2ae017ee1f9390df5e68ebe79a8d4745",
|
||||
"native[1].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"package.hashPath[0]": "stellaops.runtime.selfcontained.2.1.0.nupkg.sha512",
|
||||
"package.id": "StellaOps.Runtime.SelfContained",
|
||||
@@ -28,7 +27,8 @@
|
||||
"package.path[0]": "stellaops.runtime.selfcontained/2.1.0",
|
||||
"package.serviceable": "true",
|
||||
"package.sha512[0]": "sha512-FAKE_RUNTIME_SHA==",
|
||||
"package.version": "2.1.0"
|
||||
"package.version": "2.1.0",
|
||||
"provenance": "manifest"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
@@ -42,14 +42,7 @@
|
||||
"source": "native",
|
||||
"locator": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||
"value": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||
"sha256": "c22d4a6584a3bb8fad4d255d1ab9e5a80d553eec35ea8dfcc2dd750e8581d3cb"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "native",
|
||||
"locator": "runtimes/win-x64/native/stellaopsnative.dll",
|
||||
"value": "runtimes/win-x64/native/stellaopsnative.dll",
|
||||
"sha256": "29cddd69702aedc715050304bec85aad2ae017ee1f9390df5e68ebe79a8d4745"
|
||||
"sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -60,41 +53,41 @@
|
||||
"name": "StellaOps.Toolkit",
|
||||
"version": "1.2.3",
|
||||
"type": "nuget",
|
||||
"usedByEntrypoint": true,
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
|
||||
"assembly[0].fileVersion": "1.2.3.0",
|
||||
"assembly[0].path": "lib/net10.0/StellaOps.Toolkit.dll",
|
||||
"assembly[0].rid[0]": "linux-x64",
|
||||
"assembly[0].rid[1]": "win-x64",
|
||||
"assembly[0].sha256": "5b82fd11cf6c2ba6b351592587c4203f6af48b89427b954903534eac0e9f17f7",
|
||||
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"assembly[0].version": "1.2.3.0",
|
||||
"deps.path[0]": "MyApp.deps.json",
|
||||
"deps.rid[0]": "linux-x64",
|
||||
"deps.rid[1]": "win-x64",
|
||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"license.file.sha256[0]": "f94d89a576c63e8ba6ee01760c52fa7861ba609491d7c6e6c01ead5ca66b6048",
|
||||
"license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
|
||||
"package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
|
||||
"package.id": "StellaOps.Toolkit",
|
||||
"package.id.normalized": "stellaops.toolkit",
|
||||
"package.path[0]": "stellaops.toolkit/1.2.3",
|
||||
"package.serviceable": "true",
|
||||
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
||||
"package.version": "1.2.3"
|
||||
"package.version": "1.2.3",
|
||||
"provenance": "manifest"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "assembly",
|
||||
"locator": "lib/net10.0/StellaOps.Toolkit.dll",
|
||||
"value": "lib/net10.0/StellaOps.Toolkit.dll",
|
||||
"sha256": "5b82fd11cf6c2ba6b351592587c4203f6af48b89427b954903534eac0e9f17f7"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "deps.json",
|
||||
"locator": "MyApp.deps.json",
|
||||
"value": "StellaOps.Toolkit/1.2.3"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "license",
|
||||
"locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
|
||||
"sha256": "f94d89a576c63e8ba6ee01760c52fa7861ba609491d7c6e6c01ead5ca66b6048"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>StellaOps.Runtime.SelfContained</id>
|
||||
<version>2.1.0</version>
|
||||
<authors>StellaOps</authors>
|
||||
<description>Runtime bundle used for self-contained analyzer fixtures.</description>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
<licenseUrl>https://stella-ops.example/licenses/runtime</licenseUrl>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -0,0 +1,6 @@
|
||||
StellaOps Toolkit License
|
||||
=========================
|
||||
|
||||
Reusable toolkit licensing terms for analyzer fixtures.
|
||||
|
||||
This document is intentionally short for deterministic hashing tests.
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>StellaOps.Toolkit</id>
|
||||
<version>1.2.3</version>
|
||||
<authors>StellaOps</authors>
|
||||
<description>Toolkit package for self-contained analyzer fixtures.</description>
|
||||
<license type="file">LICENSE.txt</license>
|
||||
<licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -9,21 +9,7 @@
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"assembly[0].assetPath": "lib/net9.0/Microsoft.Extensions.Logging.dll",
|
||||
"assembly[0].authenticode.issuer": "CN=StellaOps Root",
|
||||
"assembly[0].authenticode.notAfter": "2026-01-01T00:00:00.000Z",
|
||||
"assembly[0].authenticode.notBefore": "2025-01-01T00:00:00.000Z",
|
||||
"assembly[0].authenticode.serialNumber": "0123456789ABCDEF",
|
||||
"assembly[0].authenticode.subject": "CN=StellaOps Test Signing",
|
||||
"assembly[0].authenticode.thumbprint": "AA11BB22CC33DD44EE55FF66GG77HH88II99JJ00",
|
||||
"assembly[0].company": "Microsoft Corporation",
|
||||
"assembly[0].fileDescription": "Microsoft.Extensions.Logging",
|
||||
"assembly[0].fileVersion": "9.0.24.52809",
|
||||
"assembly[0].path": "packages/microsoft.extensions.logging/9.0.0/lib/net9.0/Microsoft.Extensions.Logging.dll",
|
||||
"assembly[0].product": "Microsoft\u00ae .NET",
|
||||
"assembly[0].productVersion": "9.0.0+9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3",
|
||||
"assembly[0].publicKeyToken": "adb9793829ddae60",
|
||||
"assembly[0].sha256": "faed6cb5c9ca0d6077feaeb2df251251adccf0241f7a80b91c58e014cd5ad48f",
|
||||
"assembly[0].strongName": "Microsoft.Extensions.Logging, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"assembly[0].version": "9.0.0.0",
|
||||
"assembly[1].assetPath": "runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll",
|
||||
@@ -32,22 +18,17 @@
|
||||
"deps.path[0]": "Signed.App.deps.json",
|
||||
"deps.rid[0]": "linux-x64",
|
||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"license.expression[0]": "MIT",
|
||||
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||
"package.id": "Microsoft.Extensions.Logging",
|
||||
"package.id.normalized": "microsoft.extensions.logging",
|
||||
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
||||
"package.serviceable": "true",
|
||||
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
||||
"package.version": "9.0.0"
|
||||
"package.version": "9.0.0",
|
||||
"provenance": "manifest"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "assembly",
|
||||
"locator": "packages/microsoft.extensions.logging/9.0.0/lib/net9.0/Microsoft.Extensions.Logging.dll",
|
||||
"value": "lib/net9.0/Microsoft.Extensions.Logging.dll",
|
||||
"sha256": "faed6cb5c9ca0d6077feaeb2df251251adccf0241f7a80b91c58e014cd5ad48f"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "deps.json",
|
||||
@@ -56,4 +37,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Microsoft.Extensions.Logging</id>
|
||||
<version>9.0.0</version>
|
||||
<authors>Microsoft</authors>
|
||||
<description>Signed logging package fixture.</description>
|
||||
<license type="expression">MIT</license>
|
||||
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -22,13 +22,15 @@
|
||||
"deps.rid[0]": "linux-x64",
|
||||
"deps.rid[1]": "win-x86",
|
||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"license.expression[0]": "MIT",
|
||||
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||
"package.id": "Microsoft.Extensions.Logging",
|
||||
"package.id.normalized": "microsoft.extensions.logging",
|
||||
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
||||
"package.serviceable": "true",
|
||||
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
||||
"package.version": "9.0.0"
|
||||
"package.version": "9.0.0",
|
||||
"provenance": "manifest"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
@@ -56,13 +58,16 @@
|
||||
"deps.path[0]": "Sample.App.deps.json",
|
||||
"deps.rid[0]": "linux-x64",
|
||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||
"license.file.sha256[0]": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c",
|
||||
"license.file[0]": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
|
||||
"package.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
|
||||
"package.id": "StellaOps.Toolkit",
|
||||
"package.id.normalized": "stellaops.toolkit",
|
||||
"package.path[0]": "stellaops.toolkit/1.2.3",
|
||||
"package.serviceable": "true",
|
||||
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
||||
"package.version": "1.2.3"
|
||||
"package.version": "1.2.3",
|
||||
"provenance": "manifest"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
@@ -70,7 +75,13 @@
|
||||
"source": "deps.json",
|
||||
"locator": "Sample.App.deps.json",
|
||||
"value": "StellaOps.Toolkit/1.2.3"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "license",
|
||||
"locator": "packages/stellaops.toolkit/1.2.3/LICENSE.txt",
|
||||
"sha256": "604e182900b0ecb1ffb911c817bcbd148a31b8f55ad392a3b770be8005048c5c"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Microsoft.Extensions.Logging</id>
|
||||
<version>9.0.0</version>
|
||||
<authors>Microsoft</authors>
|
||||
<description>Logging abstractions for StellaOps test fixture.</description>
|
||||
<license type="expression">MIT</license>
|
||||
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -0,0 +1,7 @@
|
||||
StellaOps Toolkit License
|
||||
=========================
|
||||
|
||||
This sample license is provided for test fixtures only.
|
||||
|
||||
Permission is granted to use, copy, modify, and distribute this fixture
|
||||
for the purpose of automated testing.
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>StellaOps.Toolkit</id>
|
||||
<version>1.2.3</version>
|
||||
<authors>StellaOps</authors>
|
||||
<description>Toolkit sample package for analyzer fixtures.</description>
|
||||
<license type="file">LICENSE.txt</license>
|
||||
<licenseUrl>https://stella-ops.example/licenses/toolkit</licenseUrl>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -1,24 +1,4 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "bin::sha256:22caa7413d89026b52db64c8abc254bf9e7647ab9216e79c6972a39451f8c41e",
|
||||
"name": "unknown_tool",
|
||||
"type": "bin",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"binary.path": "usr/local/bin/unknown_tool",
|
||||
"binary.sha256": "22caa7413d89026b52db64c8abc254bf9e7647ab9216e79c6972a39451f8c41e",
|
||||
"provenance": "binary"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "binary",
|
||||
"locator": "usr/local/bin/unknown_tool",
|
||||
"sha256": "22caa7413d89026b52db64c8abc254bf9e7647ab9216e79c6972a39451f8c41e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "purl::pkg:cargo/my_app@0.1.0",
|
||||
@@ -26,22 +6,14 @@
|
||||
"name": "my_app",
|
||||
"version": "0.1.0",
|
||||
"type": "cargo",
|
||||
"usedByEntrypoint": true,
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"binary.paths": "usr/local/bin/my_app",
|
||||
"binary.sha256": "a95a4f4854bf973deacbd937bd1189fc3d0eef7a4fd4f7960f37cf66162c82fd",
|
||||
"cargo.lock.path": "Cargo.lock",
|
||||
"fingerprint.profile": "debug",
|
||||
"fingerprint.targetKind": "bin",
|
||||
"source": "registry\u002Bhttps://github.com/rust-lang/crates.io-index"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "binary",
|
||||
"locator": "usr/local/bin/my_app",
|
||||
"sha256": "a95a4f4854bf973deacbd937bd1189fc3d0eef7a4fd4f7960f37cf66162c82fd"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "cargo.fingerprint",
|
||||
@@ -87,4 +59,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -1,43 +1,43 @@
|
||||
# StellaOps Scanner — Language Analyzer Implementation Plan (2025Q4)
|
||||
|
||||
> **Goal.** Deliver best-in-class language analyzers that outperform competitors on fidelity, determinism, and offline readiness while integrating tightly with Scanner Worker orchestration and SBOM composition.
|
||||
|
||||
All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java analyzer) are complete. Each sprint is sized for a focused guild (≈1–1.5 weeks) and produces definitive gates for downstream teams (Emit, Policy, Scheduler).
|
||||
|
||||
---
|
||||
|
||||
## Sprint LA1 — Node Analyzer & Workspace Intelligence (Tasks 10-302, 10-307, 10-308, 10-309 subset) *(DOING — 2025-10-19)*
|
||||
- **Scope:** Resolve hoisted `node_modules`, PNPM structures, Yarn Berry Plug'n'Play, symlinked workspaces, and detect security-sensitive scripts.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Node` plug-in with manifest + DI registration.
|
||||
- Deterministic walker supporting >100 k modules with streaming JSON parsing.
|
||||
- Workspace graph persisted as analyzer metadata (`package.json` provenance + symlink target proofs).
|
||||
- **Acceptance Metrics:**
|
||||
- 10 k module fixture scans <1.8 s on 4 vCPU (p95).
|
||||
- Memory ceiling <220 MB (tracked via deterministic benchmark harness).
|
||||
- All symlink targets canonicalized; path traversal guarded.
|
||||
- **Gate Artifacts:**
|
||||
- `Fixtures/lang/node/**` golden outputs.
|
||||
- Analyzer benchmark CSV + flamegraph (commit under `bench/Scanner.Analyzers`).
|
||||
- Worker integration sample enabling Node analyzer via manifest.
|
||||
# StellaOps Scanner — Language Analyzer Implementation Plan (2025Q4)
|
||||
|
||||
> **Goal.** Deliver best-in-class language analyzers that outperform competitors on fidelity, determinism, and offline readiness while integrating tightly with Scanner Worker orchestration and SBOM composition.
|
||||
|
||||
All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java analyzer) are complete. Each sprint is sized for a focused guild (≈1–1.5 weeks) and produces definitive gates for downstream teams (Emit, Policy, Scheduler).
|
||||
|
||||
---
|
||||
|
||||
## Sprint LA1 — Node Analyzer & Workspace Intelligence (Tasks 10-302, 10-307, 10-308, 10-309 subset) *(DOING — 2025-10-19)*
|
||||
- **Scope:** Resolve hoisted `node_modules`, PNPM structures, Yarn Berry Plug'n'Play, symlinked workspaces, and detect security-sensitive scripts.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Node` plug-in with manifest + DI registration.
|
||||
- Deterministic walker supporting >100 k modules with streaming JSON parsing.
|
||||
- Workspace graph persisted as analyzer metadata (`package.json` provenance + symlink target proofs).
|
||||
- **Acceptance Metrics:**
|
||||
- 10 k module fixture scans <1.8 s on 4 vCPU (p95).
|
||||
- Memory ceiling <220 MB (tracked via deterministic benchmark harness).
|
||||
- All symlink targets canonicalized; path traversal guarded.
|
||||
- **Gate Artifacts:**
|
||||
- `Fixtures/lang/node/**` golden outputs.
|
||||
- Analyzer benchmark CSV + flamegraph (commit under `bench/Scanner.Analyzers`).
|
||||
- Worker integration sample enabling Node analyzer via manifest.
|
||||
- **Progress (2025-10-21):** Module walker with package-lock/yarn/pnpm resolution, workspace attribution, integrity metadata, and deterministic fixture harness committed; Node tasks 10-302A/B remain green. Shared component mapper + canonical result harness landed, closing tasks 10-307/308. Script metadata & telemetry (10-302C) emit policy hints, hashed evidence, and feed `scanner_analyzer_node_scripts_total` into Worker OpenTelemetry pipeline. Restart-time packaging closed (10-309): manifest added, Worker language catalog loads the Node analyzer, integration tests cover dispatch + layer fragments, and Offline Kit docs call out bundled language plug-ins.
|
||||
|
||||
## Sprint LA2 — Python Analyzer & Entry Point Attribution (Tasks 10-303, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Parse `*.dist-info`, `RECORD` hashes, entry points, and pip-installed editable packages; integrate usage hints from EntryTrace.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Python` plug-in.
|
||||
- RECORD hash validation with optional Zip64 support for `.whl` caches.
|
||||
- Entry-point mapping into `UsageFlags` for Emit stage.
|
||||
- **Acceptance Metrics:**
|
||||
- Hash verification throughput ≥75 MB/s sustained with streaming reader.
|
||||
- False-positive rate for editable installs <1 % on curated fixtures.
|
||||
- Determinism check across CPython 3.8–3.12 generated metadata.
|
||||
|
||||
## Sprint LA2 — Python Analyzer & Entry Point Attribution (Tasks 10-303, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Parse `*.dist-info`, `RECORD` hashes, entry points, and pip-installed editable packages; integrate usage hints from EntryTrace.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Python` plug-in.
|
||||
- RECORD hash validation with optional Zip64 support for `.whl` caches.
|
||||
- Entry-point mapping into `UsageFlags` for Emit stage.
|
||||
- **Acceptance Metrics:**
|
||||
- Hash verification throughput ≥75 MB/s sustained with streaming reader.
|
||||
- False-positive rate for editable installs <1 % on curated fixtures.
|
||||
- Determinism check across CPython 3.8–3.12 generated metadata.
|
||||
- **Gate Artifacts:**
|
||||
- Golden fixtures for `site-packages`, virtualenv, and layered pip caches.
|
||||
- Usage hint propagation tests (EntryTrace → analyzer → SBOM).
|
||||
- Metrics counters (`scanner_analyzer_python_components_total`) documented.
|
||||
- **Progress (2025-10-21):** Python analyzer landed; Tasks 10-303A/B/C are DONE with dist-info parsing, RECORD verification, editable install detection, and deterministic `simple-venv` fixture + benchmark hooks recorded.
|
||||
|
||||
|
||||
## Sprint LA3 — Go Analyzer & Build Info Synthesis (Tasks 10-304, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Extract Go build metadata from `.note.go.buildid`, embedded module info, and fallback to `bin:{sha256}`; surface VCS provenance.
|
||||
- **Deliverables:**
|
||||
@@ -51,67 +51,67 @@ All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java ana
|
||||
- **Gate Artifacts:**
|
||||
- Benchmarks vs competitor open-source tool (Trivy or Syft) demonstrating faster metadata extraction.
|
||||
- Documentation snippet explaining VCS metadata fields for Policy team.
|
||||
- **Progress (2025-10-22):** Build-info decoder shipped with DWARF-string fallback for `vcs.*` markers, plus cached metadata keyed by binary length/timestamp. Added Go test fixtures covering build-info and DWARF-only binaries with deterministic goldens; analyzer now emits `go.dwarf` evidence alongside `go.buildinfo` metadata to feed downstream provenance rules. Completed stripped-binary heuristics with deterministic `golang::bin::sha256` components and a new `stripped` fixture to guard quiet-provenance behaviour.
|
||||
|
||||
## Sprint LA4 — .NET Analyzer & RID Variants (Tasks 10-305, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Parse `*.deps.json`, `runtimeconfig.json`, assembly metadata, and RID-specific assets; correlate with native dependencies.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.DotNet` plug-in.
|
||||
- Strong-name + Authenticode optional verification when offline cert bundle provided.
|
||||
- RID-aware component grouping with fallback to `bin:{sha256}` for self-contained apps.
|
||||
- **Acceptance Metrics:**
|
||||
- Multi-target app fixture processed <1.2 s; memory <250 MB.
|
||||
- RID variant collapse reduces component explosion by ≥40 % vs naive listing.
|
||||
- All security metadata (signing Publisher, timestamp) surfaced deterministically.
|
||||
- **Progress (2025-10-22):** Build-info decoder shipped with DWARF-string fallback for `vcs.*` markers, plus cached metadata keyed by binary length/timestamp. Added Go test fixtures covering build-info and DWARF-only binaries with deterministic goldens; analyzer now emits `go.dwarf` evidence alongside `go.buildinfo` metadata to feed downstream provenance rules. Completed stripped-binary heuristics with deterministic `golang::bin::sha256` components and a new `stripped` fixture to guard quiet-provenance behaviour. Heuristic fallbacks now emit `scanner_analyzer_golang_heuristic_total{indicator,version_hint}` counters, and shared buffer pooling (`ArrayPool<byte>`) keeps concurrent scans allocation-lite. Bench harness (`bench/Scanner.Analyzers/config.json`) gained a dedicated Go scenario with baseline mean 4.02 ms; comparison against Syft v1.29.1 on the same fixture shows a 22 % speed advantage (see `bench/Scanner.Analyzers/lang/go/syft-comparison-20251021.csv`).
|
||||
|
||||
## Sprint LA4 — .NET Analyzer & RID Variants (Tasks 10-305, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Parse `*.deps.json`, `runtimeconfig.json`, assembly metadata, and RID-specific assets; correlate with native dependencies.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.DotNet` plug-in.
|
||||
- Strong-name + Authenticode optional verification when offline cert bundle provided.
|
||||
- RID-aware component grouping with fallback to `bin:{sha256}` for self-contained apps.
|
||||
- **Acceptance Metrics:**
|
||||
- Multi-target app fixture processed <1.2 s; memory <250 MB.
|
||||
- RID variant collapse reduces component explosion by ≥40 % vs naive listing.
|
||||
- All security metadata (signing Publisher, timestamp) surfaced deterministically.
|
||||
- **Gate Artifacts:**
|
||||
- Signed .NET sample apps (framework-dependent & self-contained) under `samples/scanner/lang/dotnet/`.
|
||||
- Tests verifying dual runtimeconfig merge logic.
|
||||
- Guidance for Policy on license propagation from NuGet metadata.
|
||||
- **Progress (2025-10-22):** Completed task 10-305A with a deterministic deps/runtimeconfig ingest pipeline producing `pkg:nuget` components across RID targets. Added dotnet fixture + golden output to the shared harness, wired analyzer plugin availability, and surfaced RID metadata in component records for downstream emit/diff work.
|
||||
|
||||
## Sprint LA5 — Rust Analyzer & Binary Fingerprinting (Tasks 10-306, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Detect crates via metadata in `.fingerprint`, Cargo.lock fragments, or embedded `rustc` markers; robust fallback to binary hash classification.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Rust` plug-in.
|
||||
- Symbol table heuristics capable of attributing stripped binaries by leveraging `.comment` and section names without violating determinism.
|
||||
- Quiet-provenance flags to differentiate heuristics from hard evidence.
|
||||
- **Acceptance Metrics:**
|
||||
- Accurate crate attribution ≥85 % on curated Cargo workspace fixtures.
|
||||
- Heuristic fallback clearly labeled; no false “certain” claims.
|
||||
- Analyzer completes <1 s on 500 binary corpus.
|
||||
- **Gate Artifacts:**
|
||||
- Fixtures covering cargo workspaces, binaries with embedded metadata stripped.
|
||||
- ADR documenting heuristic boundaries + risk mitigations.
|
||||
|
||||
## Sprint LA6 — Shared Evidence Enhancements & Worker Integration (Tasks 10-307, 10-308, 10-309 finalization)
|
||||
- **Scope:** Finalize shared helpers, deterministic harness expansion, Worker/Emit wiring, and macro benchmarks.
|
||||
- **Deliverables:**
|
||||
- Consolidated `LanguageComponentWriter` extensions for license, vulnerability hints, and usage propagation.
|
||||
- Worker dispatcher loading plug-ins via manifest registry + health checks.
|
||||
- Combined analyzer benchmark suite executed in CI with regression thresholds.
|
||||
- **Acceptance Metrics:**
|
||||
- Worker executes mixed analyzer suite (Java+Node+Python+Go+.NET+Rust) within SLA: warm scan <6 s, cold <25 s.
|
||||
- CI determinism guard catches output drift (>0 diff tolerance) across all fixtures.
|
||||
- Telemetry coverage: each analyzer emits timing + component counters.
|
||||
- **Gate Artifacts:**
|
||||
- `SPRINTS_LANG_IMPLEMENTATION_PLAN.md` progress log updated (this file).
|
||||
- `bench/Scanner.Analyzers/lang-matrix.csv` recorded + referenced in docs.
|
||||
- Ops notes for packaging plug-ins into Offline Kit.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Sprint Considerations
|
||||
- **Security:** All analyzers must enforce path canonicalization, guard against zip-slip, and expose provenance classifications (`observed`, `heuristic`, `attested`).
|
||||
- **Offline-first:** No network calls; rely on cached metadata and optional offline bundles (license texts, signature roots).
|
||||
- **Determinism:** Normalise timestamps to `0001-01-01T00:00:00Z` when persisting synthetic data; sort collections by stable keys.
|
||||
- **Benchmarking:** Extend `bench/Scanner.Analyzers` to compare against open-source scanners (Syft/Trivy) and document performance wins.
|
||||
- **Hand-offs:** Emit guild requires consistent component schemas; Policy needs license + provenance metadata; Scheduler depends on usage flags for ImpactIndex.
|
||||
|
||||
## Tracking & Reporting
|
||||
- Update `TASKS.md` per sprint (TODO → DOING → DONE) with date stamps.
|
||||
- Log sprint summaries in `docs/updates/` once each sprint lands.
|
||||
- Use module-specific CI pipeline to run analyzer suites nightly (determinism + perf).
|
||||
|
||||
---
|
||||
|
||||
**Next Action:** Start Sprint LA1 (Node Analyzer) — move tasks 10-302, 10-307, 10-308, 10-309 → DOING and spin up fixtures + benchmarks.
|
||||
- **Progress (2025-10-22):** Completed task 10-305A with a deterministic deps/runtimeconfig ingest pipeline producing `pkg:nuget` components across RID targets. Added dotnet fixture + golden output to the shared harness, wired analyzer plugin availability, and surfaced RID metadata in component records for downstream emit/diff work. License provenance and quiet flagging now ride through the shared helpers (task 10-307D), including nuspec license expression/file ingestion, manifest provenance tagging, and concurrency-safe file metadata caching with new parallel tests.
|
||||
|
||||
## Sprint LA5 — Rust Analyzer & Binary Fingerprinting (Tasks 10-306, 10-307, 10-308, 10-309 subset)
|
||||
- **Scope:** Detect crates via metadata in `.fingerprint`, Cargo.lock fragments, or embedded `rustc` markers; robust fallback to binary hash classification.
|
||||
- **Deliverables:**
|
||||
- `StellaOps.Scanner.Analyzers.Lang.Rust` plug-in.
|
||||
- Symbol table heuristics capable of attributing stripped binaries by leveraging `.comment` and section names without violating determinism.
|
||||
- Quiet-provenance flags to differentiate heuristics from hard evidence.
|
||||
- **Acceptance Metrics:**
|
||||
- Accurate crate attribution ≥85 % on curated Cargo workspace fixtures.
|
||||
- Heuristic fallback clearly labeled; no false “certain” claims.
|
||||
- Analyzer completes <1 s on 500 binary corpus.
|
||||
- **Gate Artifacts:**
|
||||
- Fixtures covering cargo workspaces, binaries with embedded metadata stripped.
|
||||
- ADR documenting heuristic boundaries + risk mitigations.
|
||||
|
||||
## Sprint LA6 — Shared Evidence Enhancements & Worker Integration (Tasks 10-307, 10-308, 10-309 finalization)
|
||||
- **Scope:** Finalize shared helpers, deterministic harness expansion, Worker/Emit wiring, and macro benchmarks.
|
||||
- **Deliverables:**
|
||||
- Consolidated `LanguageComponentWriter` extensions for license, vulnerability hints, and usage propagation.
|
||||
- Worker dispatcher loading plug-ins via manifest registry + health checks.
|
||||
- Combined analyzer benchmark suite executed in CI with regression thresholds.
|
||||
- **Acceptance Metrics:**
|
||||
- Worker executes mixed analyzer suite (Java+Node+Python+Go+.NET+Rust) within SLA: warm scan <6 s, cold <25 s.
|
||||
- CI determinism guard catches output drift (>0 diff tolerance) across all fixtures.
|
||||
- Telemetry coverage: each analyzer emits timing + component counters.
|
||||
- **Gate Artifacts:**
|
||||
- `SPRINTS_LANG_IMPLEMENTATION_PLAN.md` progress log updated (this file).
|
||||
- `bench/Scanner.Analyzers/lang-matrix.csv` recorded + referenced in docs.
|
||||
- Ops notes for packaging plug-ins into Offline Kit.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Sprint Considerations
|
||||
- **Security:** All analyzers must enforce path canonicalization, guard against zip-slip, and expose provenance classifications (`observed`, `heuristic`, `attested`).
|
||||
- **Offline-first:** No network calls; rely on cached metadata and optional offline bundles (license texts, signature roots).
|
||||
- **Determinism:** Normalise timestamps to `0001-01-01T00:00:00Z` when persisting synthetic data; sort collections by stable keys.
|
||||
- **Benchmarking:** Extend `bench/Scanner.Analyzers` to compare against open-source scanners (Syft/Trivy) and document performance wins.
|
||||
- **Hand-offs:** Emit guild requires consistent component schemas; Policy needs license + provenance metadata; Scheduler depends on usage flags for ImpactIndex.
|
||||
|
||||
## Tracking & Reporting
|
||||
- Update `TASKS.md` per sprint (TODO → DOING → DONE) with date stamps.
|
||||
- Log sprint summaries in `docs/updates/` once each sprint lands.
|
||||
- Use module-specific CI pipeline to run analyzer suites nightly (determinism + perf).
|
||||
|
||||
---
|
||||
|
||||
**Next Action:** Start Sprint LA1 (Node Analyzer) — move tasks 10-302, 10-307, 10-308, 10-309 → DOING and spin up fixtures + benchmarks.
|
||||
|
||||
Reference in New Issue
Block a user