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 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 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 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
|
### 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.
|
- 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
|
- **Sprint 10** · Backlog
|
||||||
- Team: TBD
|
- Team: TBD
|
||||||
- Path: `src/StellaOps.Scanner.Analyzers.Lang.DotNet/TASKS.md`
|
- 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)
|
• Prereqs: SCANNER-ANALYZERS-LANG-10-305C (Wave 3)
|
||||||
• Current: TODO
|
• Current: DONE 2025-10-22
|
||||||
- Path: `src/StellaOps.Scanner.Analyzers.Lang.Go/TASKS.md`
|
- 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.
|
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)
|
• Prereqs: SCANNER-ANALYZERS-LANG-10-304C (Wave 3)
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ internal sealed class DotNetPackageBuilder
|
|||||||
var nativeAssets = CollectNativeMetadata(cancellationToken);
|
var nativeAssets = CollectNativeMetadata(cancellationToken);
|
||||||
AddNativeMetadata(metadata, nativeAssets);
|
AddNativeMetadata(metadata, nativeAssets);
|
||||||
|
|
||||||
|
AppendLicenseMetadata(metadata, cancellationToken);
|
||||||
|
AddProvenanceMetadata(metadata);
|
||||||
|
|
||||||
metadata.Sort(static (left, right) => string.CompareOrdinal(left.Key, right.Key));
|
metadata.Sort(static (left, right) => string.CompareOrdinal(left.Key, right.Key));
|
||||||
|
|
||||||
var evidence = _evidence
|
var evidence = _evidence
|
||||||
@@ -403,6 +406,184 @@ internal sealed class DotNetPackageBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AppendLicenseMetadata(List<KeyValuePair<string, string?>> metadata, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (metadata is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
var expressions = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var urls = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var licenseFiles = new SortedDictionary<string, string?>(StringComparer.Ordinal);
|
||||||
|
var processedNuspecs = new HashSet<string>(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (var packagePath in _packagePaths)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
foreach (var basePath in EnumeratePackageBases(packagePath))
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var platformRelative = ConvertToPlatformPath(basePath);
|
||||||
|
var absoluteDirectory = SafeResolveDirectory(platformRelative);
|
||||||
|
if (absoluteDirectory is null || !Directory.Exists(absoluteDirectory))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var nuspecPath in Directory.EnumerateFiles(absoluteDirectory, "*.nuspec", SearchOption.TopDirectoryOnly))
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var normalizedNuspec = NormalizeFullPath(nuspecPath);
|
||||||
|
if (!processedNuspecs.Add(normalizedNuspec))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DotNetLicenseCache.TryGetLicenseInfo(nuspecPath, out var licenseInfo) || licenseInfo is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var expression in licenseInfo.Expressions)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(expression))
|
||||||
|
{
|
||||||
|
expressions.Add(expression.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var url in licenseInfo.Urls)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
urls.Add(url.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var fileRelative in licenseInfo.Files)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(fileRelative))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedRelative = NormalizePath(fileRelative);
|
||||||
|
if (string.IsNullOrEmpty(normalizedRelative))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var platformRelativeFile = ConvertToPlatformPath(normalizedRelative);
|
||||||
|
var absoluteFile = SafeCombine(absoluteDirectory, platformRelativeFile);
|
||||||
|
if (absoluteFile is null || !File.Exists(absoluteFile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!absoluteFile.StartsWith(absoluteDirectory, OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativeToRoot = NormalizePath(_context.GetRelativePath(absoluteFile));
|
||||||
|
if (string.IsNullOrEmpty(relativeToRoot))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!licenseFiles.ContainsKey(relativeToRoot))
|
||||||
|
{
|
||||||
|
DotNetFileMetadataCache.TryGetSha256(absoluteFile, out var shaValue);
|
||||||
|
licenseFiles[relativeToRoot] = shaValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expressions.Count > 0)
|
||||||
|
{
|
||||||
|
AddIndexed(metadata, "license.expression", expressions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urls.Count > 0)
|
||||||
|
{
|
||||||
|
AddIndexed(metadata, "license.url", urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (licenseFiles.Count > 0)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
foreach (var pair in licenseFiles)
|
||||||
|
{
|
||||||
|
metadata.Add(new KeyValuePair<string, string?>($"license.file[{index}]", pair.Key));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pair.Value))
|
||||||
|
{
|
||||||
|
metadata.Add(new KeyValuePair<string, string?>($"license.file.sha256[{index}]", pair.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_evidence.Add(new LanguageComponentEvidence(
|
||||||
|
LanguageEvidenceKind.File,
|
||||||
|
"license",
|
||||||
|
pair.Key,
|
||||||
|
Value: null,
|
||||||
|
Sha256: pair.Value));
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddProvenanceMetadata(ICollection<KeyValuePair<string, string?>> metadata)
|
||||||
|
{
|
||||||
|
if (!metadata.Any(static pair => string.Equals(pair.Key, "provenance", StringComparison.Ordinal)))
|
||||||
|
{
|
||||||
|
metadata.Add(new KeyValuePair<string, string?>("provenance", "manifest"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? SafeResolveDirectory(string relativePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _context.ResolvePath(relativePath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? SafeCombine(string baseDirectory, string relativePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(Path.Combine(baseDirectory, relativePath));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeFullPath(string path)
|
||||||
|
{
|
||||||
|
var fullPath = Path.GetFullPath(path);
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
fullPath = fullPath.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddRuntimeAssets(DotNetLibrary library)
|
private void AddRuntimeAssets(DotNetLibrary library)
|
||||||
{
|
{
|
||||||
foreach (var asset in library.RuntimeAssets)
|
foreach (var asset in library.RuntimeAssets)
|
||||||
@@ -562,7 +743,7 @@ internal sealed class DotNetPackageBuilder
|
|||||||
return string.IsNullOrEmpty(normalized) ? string.Empty : normalized;
|
return string.IsNullOrEmpty(normalized) ? string.Empty : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ConvertToPlatformPath(string path)
|
internal static string ConvertToPlatformPath(string path)
|
||||||
=> string.IsNullOrEmpty(path) ? "." : path.Replace('/', Path.DirectorySeparatorChar);
|
=> string.IsNullOrEmpty(path) ? "." : path.Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
private static string CombineRelative(string basePath, string relativePath)
|
private static string CombineRelative(string basePath, string relativePath)
|
||||||
@@ -583,54 +764,6 @@ internal sealed class DotNetPackageBuilder
|
|||||||
return NormalizePath($"{left}/{right}");
|
return NormalizePath($"{left}/{right}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? ComputeSha256(string path)
|
|
||||||
{
|
|
||||||
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
||||||
using var sha = SHA256.Create();
|
|
||||||
var hash = sha.ComputeHash(stream);
|
|
||||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AssemblyName? TryGetAssemblyName(string path)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return AssemblyName.GetAssemblyName(path);
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (BadImageFormatException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (FileLoadException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FileVersionInfo? TryGetFileVersionInfo(string path)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return FileVersionInfo.GetVersionInfo(path);
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? FormatPublicKeyToken(byte[]? token)
|
private static string? FormatPublicKeyToken(byte[]? token)
|
||||||
{
|
{
|
||||||
if (token is null || token.Length == 0)
|
if (token is null || token.Length == 0)
|
||||||
@@ -819,9 +952,9 @@ internal sealed class DotNetPackageBuilder
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var relativePath = NormalizePath(context.GetRelativePath(absolutePath));
|
var relativePath = NormalizePath(context.GetRelativePath(absolutePath));
|
||||||
var sha256 = ComputeSha256(absolutePath);
|
DotNetFileMetadataCache.TryGetSha256(absolutePath, out var sha256);
|
||||||
var assemblyName = TryGetAssemblyName(absolutePath);
|
DotNetFileMetadataCache.TryGetAssemblyName(absolutePath, out var assemblyName);
|
||||||
var versionInfo = TryGetFileVersionInfo(absolutePath);
|
DotNetFileMetadataCache.TryGetFileVersionInfo(absolutePath, out var versionInfo);
|
||||||
|
|
||||||
DotNetAuthenticodeMetadata? authenticode = null;
|
DotNetAuthenticodeMetadata? authenticode = null;
|
||||||
if (authenticodeInspector is not null)
|
if (authenticodeInspector is not null)
|
||||||
@@ -963,7 +1096,7 @@ internal sealed class DotNetPackageBuilder
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var relativePath = NormalizePath(context.GetRelativePath(absolutePath));
|
var relativePath = NormalizePath(context.GetRelativePath(absolutePath));
|
||||||
var sha256 = ComputeSha256(absolutePath);
|
DotNetFileMetadataCache.TryGetSha256(absolutePath, out var sha256);
|
||||||
return new NativeAssetFileMetadata(
|
return new NativeAssetFileMetadata(
|
||||||
AbsolutePath: absolutePath,
|
AbsolutePath: absolutePath,
|
||||||
RelativePath: relativePath,
|
RelativePath: relativePath,
|
||||||
|
|||||||
@@ -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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using StellaOps.Scanner.Analyzers.Lang.DotNet;
|
using StellaOps.Scanner.Analyzers.Lang.DotNet;
|
||||||
@@ -79,6 +80,29 @@ public sealed class DotNetLanguageAnalyzerTests
|
|||||||
usageHints);
|
usageHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
|
||||||
|
{
|
||||||
|
var cancellationToken = TestContext.Current.CancellationToken;
|
||||||
|
var fixturePath = TestPaths.ResolveFixture("lang", "dotnet", "selfcontained");
|
||||||
|
|
||||||
|
var analyzers = new ILanguageAnalyzer[]
|
||||||
|
{
|
||||||
|
new DotNetLanguageAnalyzer()
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
private sealed class StubAuthenticodeInspector : IDotNetAuthenticodeInspector
|
||||||
{
|
{
|
||||||
public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken)
|
public DotNetAuthenticodeMetadata? TryInspect(string assemblyPath, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -12,15 +12,14 @@
|
|||||||
"deps.rid[0]": "linux-x64",
|
"deps.rid[0]": "linux-x64",
|
||||||
"deps.rid[1]": "win-x64",
|
"deps.rid[1]": "win-x64",
|
||||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
|
"license.expression[0]": "Apache-2.0",
|
||||||
"native[0].assetPath": "runtimes/linux-x64/native/libstellaopsnative.so",
|
"native[0].assetPath": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||||
"native[0].path": "runtimes/linux-x64/native/libstellaopsnative.so",
|
"native[0].path": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||||
"native[0].rid[0]": "linux-x64",
|
"native[0].rid[0]": "linux-x64",
|
||||||
"native[0].sha256": "c22d4a6584a3bb8fad4d255d1ab9e5a80d553eec35ea8dfcc2dd750e8581d3cb",
|
"native[0].sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0",
|
||||||
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
"native[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
"native[1].assetPath": "runtimes/win-x64/native/stellaopsnative.dll",
|
"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].rid[0]": "win-x64",
|
||||||
"native[1].sha256": "29cddd69702aedc715050304bec85aad2ae017ee1f9390df5e68ebe79a8d4745",
|
|
||||||
"native[1].tfm[0]": ".NETCoreApp,Version=v10.0",
|
"native[1].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
"package.hashPath[0]": "stellaops.runtime.selfcontained.2.1.0.nupkg.sha512",
|
"package.hashPath[0]": "stellaops.runtime.selfcontained.2.1.0.nupkg.sha512",
|
||||||
"package.id": "StellaOps.Runtime.SelfContained",
|
"package.id": "StellaOps.Runtime.SelfContained",
|
||||||
@@ -28,7 +27,8 @@
|
|||||||
"package.path[0]": "stellaops.runtime.selfcontained/2.1.0",
|
"package.path[0]": "stellaops.runtime.selfcontained/2.1.0",
|
||||||
"package.serviceable": "true",
|
"package.serviceable": "true",
|
||||||
"package.sha512[0]": "sha512-FAKE_RUNTIME_SHA==",
|
"package.sha512[0]": "sha512-FAKE_RUNTIME_SHA==",
|
||||||
"package.version": "2.1.0"
|
"package.version": "2.1.0",
|
||||||
|
"provenance": "manifest"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"evidence": [
|
||||||
{
|
{
|
||||||
@@ -42,14 +42,7 @@
|
|||||||
"source": "native",
|
"source": "native",
|
||||||
"locator": "runtimes/linux-x64/native/libstellaopsnative.so",
|
"locator": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||||
"value": "runtimes/linux-x64/native/libstellaopsnative.so",
|
"value": "runtimes/linux-x64/native/libstellaopsnative.so",
|
||||||
"sha256": "c22d4a6584a3bb8fad4d255d1ab9e5a80d553eec35ea8dfcc2dd750e8581d3cb"
|
"sha256": "6cf3d2a487d6a42fc7c3e2edbc452224e99a3656287a534f1164ee6ec9daadf0"
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "file",
|
|
||||||
"source": "native",
|
|
||||||
"locator": "runtimes/win-x64/native/stellaopsnative.dll",
|
|
||||||
"value": "runtimes/win-x64/native/stellaopsnative.dll",
|
|
||||||
"sha256": "29cddd69702aedc715050304bec85aad2ae017ee1f9390df5e68ebe79a8d4745"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -60,41 +53,41 @@
|
|||||||
"name": "StellaOps.Toolkit",
|
"name": "StellaOps.Toolkit",
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"type": "nuget",
|
"type": "nuget",
|
||||||
"usedByEntrypoint": true,
|
"usedByEntrypoint": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
|
"assembly[0].assetPath": "lib/net10.0/StellaOps.Toolkit.dll",
|
||||||
"assembly[0].fileVersion": "1.2.3.0",
|
"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[0]": "linux-x64",
|
||||||
"assembly[0].rid[1]": "win-x64",
|
"assembly[0].rid[1]": "win-x64",
|
||||||
"assembly[0].sha256": "5b82fd11cf6c2ba6b351592587c4203f6af48b89427b954903534eac0e9f17f7",
|
|
||||||
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
"assembly[0].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
"assembly[0].version": "1.2.3.0",
|
"assembly[0].version": "1.2.3.0",
|
||||||
"deps.path[0]": "MyApp.deps.json",
|
"deps.path[0]": "MyApp.deps.json",
|
||||||
"deps.rid[0]": "linux-x64",
|
"deps.rid[0]": "linux-x64",
|
||||||
"deps.rid[1]": "win-x64",
|
"deps.rid[1]": "win-x64",
|
||||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
"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.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
|
||||||
"package.id": "StellaOps.Toolkit",
|
"package.id": "StellaOps.Toolkit",
|
||||||
"package.id.normalized": "stellaops.toolkit",
|
"package.id.normalized": "stellaops.toolkit",
|
||||||
"package.path[0]": "stellaops.toolkit/1.2.3",
|
"package.path[0]": "stellaops.toolkit/1.2.3",
|
||||||
"package.serviceable": "true",
|
"package.serviceable": "true",
|
||||||
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
||||||
"package.version": "1.2.3"
|
"package.version": "1.2.3",
|
||||||
|
"provenance": "manifest"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"evidence": [
|
||||||
{
|
|
||||||
"kind": "file",
|
|
||||||
"source": "assembly",
|
|
||||||
"locator": "lib/net10.0/StellaOps.Toolkit.dll",
|
|
||||||
"value": "lib/net10.0/StellaOps.Toolkit.dll",
|
|
||||||
"sha256": "5b82fd11cf6c2ba6b351592587c4203f6af48b89427b954903534eac0e9f17f7"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "file",
|
"kind": "file",
|
||||||
"source": "deps.json",
|
"source": "deps.json",
|
||||||
"locator": "MyApp.deps.json",
|
"locator": "MyApp.deps.json",
|
||||||
"value": "StellaOps.Toolkit/1.2.3"
|
"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,
|
"usedByEntrypoint": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"assembly[0].assetPath": "lib/net9.0/Microsoft.Extensions.Logging.dll",
|
"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].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].tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
"assembly[0].version": "9.0.0.0",
|
"assembly[0].version": "9.0.0.0",
|
||||||
"assembly[1].assetPath": "runtimes/linux-x64/lib/net9.0/Microsoft.Extensions.Logging.dll",
|
"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.path[0]": "Signed.App.deps.json",
|
||||||
"deps.rid[0]": "linux-x64",
|
"deps.rid[0]": "linux-x64",
|
||||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
|
"license.expression[0]": "MIT",
|
||||||
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||||
"package.id": "Microsoft.Extensions.Logging",
|
"package.id": "Microsoft.Extensions.Logging",
|
||||||
"package.id.normalized": "microsoft.extensions.logging",
|
"package.id.normalized": "microsoft.extensions.logging",
|
||||||
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
||||||
"package.serviceable": "true",
|
"package.serviceable": "true",
|
||||||
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
||||||
"package.version": "9.0.0"
|
"package.version": "9.0.0",
|
||||||
|
"provenance": "manifest"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"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",
|
"kind": "file",
|
||||||
"source": "deps.json",
|
"source": "deps.json",
|
||||||
|
|||||||
@@ -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[0]": "linux-x64",
|
||||||
"deps.rid[1]": "win-x86",
|
"deps.rid[1]": "win-x86",
|
||||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
||||||
|
"license.expression[0]": "MIT",
|
||||||
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
"package.hashPath[0]": "microsoft.extensions.logging.9.0.0.nupkg.sha512",
|
||||||
"package.id": "Microsoft.Extensions.Logging",
|
"package.id": "Microsoft.Extensions.Logging",
|
||||||
"package.id.normalized": "microsoft.extensions.logging",
|
"package.id.normalized": "microsoft.extensions.logging",
|
||||||
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
"package.path[0]": "microsoft.extensions.logging/9.0.0",
|
||||||
"package.serviceable": "true",
|
"package.serviceable": "true",
|
||||||
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
"package.sha512[0]": "sha512-FAKE_LOGGING_SHA==",
|
||||||
"package.version": "9.0.0"
|
"package.version": "9.0.0",
|
||||||
|
"provenance": "manifest"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"evidence": [
|
||||||
{
|
{
|
||||||
@@ -56,13 +58,16 @@
|
|||||||
"deps.path[0]": "Sample.App.deps.json",
|
"deps.path[0]": "Sample.App.deps.json",
|
||||||
"deps.rid[0]": "linux-x64",
|
"deps.rid[0]": "linux-x64",
|
||||||
"deps.tfm[0]": ".NETCoreApp,Version=v10.0",
|
"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.hashPath[0]": "stellaops.toolkit.1.2.3.nupkg.sha512",
|
||||||
"package.id": "StellaOps.Toolkit",
|
"package.id": "StellaOps.Toolkit",
|
||||||
"package.id.normalized": "stellaops.toolkit",
|
"package.id.normalized": "stellaops.toolkit",
|
||||||
"package.path[0]": "stellaops.toolkit/1.2.3",
|
"package.path[0]": "stellaops.toolkit/1.2.3",
|
||||||
"package.serviceable": "true",
|
"package.serviceable": "true",
|
||||||
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
"package.sha512[0]": "sha512-FAKE_TOOLKIT_SHA==",
|
||||||
"package.version": "1.2.3"
|
"package.version": "1.2.3",
|
||||||
|
"provenance": "manifest"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"evidence": [
|
||||||
{
|
{
|
||||||
@@ -70,6 +75,12 @@
|
|||||||
"source": "deps.json",
|
"source": "deps.json",
|
||||||
"locator": "Sample.App.deps.json",
|
"locator": "Sample.App.deps.json",
|
||||||
"value": "StellaOps.Toolkit/1.2.3"
|
"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",
|
"analyzerId": "rust",
|
||||||
"componentKey": "purl::pkg:cargo/my_app@0.1.0",
|
"componentKey": "purl::pkg:cargo/my_app@0.1.0",
|
||||||
@@ -26,22 +6,14 @@
|
|||||||
"name": "my_app",
|
"name": "my_app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "cargo",
|
"type": "cargo",
|
||||||
"usedByEntrypoint": true,
|
"usedByEntrypoint": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"binary.paths": "usr/local/bin/my_app",
|
|
||||||
"binary.sha256": "a95a4f4854bf973deacbd937bd1189fc3d0eef7a4fd4f7960f37cf66162c82fd",
|
|
||||||
"cargo.lock.path": "Cargo.lock",
|
"cargo.lock.path": "Cargo.lock",
|
||||||
"fingerprint.profile": "debug",
|
"fingerprint.profile": "debug",
|
||||||
"fingerprint.targetKind": "bin",
|
"fingerprint.targetKind": "bin",
|
||||||
"source": "registry\u002Bhttps://github.com/rust-lang/crates.io-index"
|
"source": "registry\u002Bhttps://github.com/rust-lang/crates.io-index"
|
||||||
},
|
},
|
||||||
"evidence": [
|
"evidence": [
|
||||||
{
|
|
||||||
"kind": "file",
|
|
||||||
"source": "binary",
|
|
||||||
"locator": "usr/local/bin/my_app",
|
|
||||||
"sha256": "a95a4f4854bf973deacbd937bd1189fc3d0eef7a4fd4f7960f37cf66162c82fd"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "file",
|
"kind": "file",
|
||||||
"source": "cargo.fingerprint",
|
"source": "cargo.fingerprint",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java ana
|
|||||||
- **Gate Artifacts:**
|
- **Gate Artifacts:**
|
||||||
- Benchmarks vs competitor open-source tool (Trivy or Syft) demonstrating faster metadata extraction.
|
- Benchmarks vs competitor open-source tool (Trivy or Syft) demonstrating faster metadata extraction.
|
||||||
- Documentation snippet explaining VCS metadata fields for Policy team.
|
- 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.
|
- **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)
|
## 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.
|
- **Scope:** Parse `*.deps.json`, `runtimeconfig.json`, assembly metadata, and RID-specific assets; correlate with native dependencies.
|
||||||
@@ -67,7 +67,7 @@ All sprints below assume prerequisites from SP10-G2 (core scaffolding + Java ana
|
|||||||
- Signed .NET sample apps (framework-dependent & self-contained) under `samples/scanner/lang/dotnet/`.
|
- Signed .NET sample apps (framework-dependent & self-contained) under `samples/scanner/lang/dotnet/`.
|
||||||
- Tests verifying dual runtimeconfig merge logic.
|
- Tests verifying dual runtimeconfig merge logic.
|
||||||
- Guidance for Policy on license propagation from NuGet metadata.
|
- 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.
|
- **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)
|
## 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.
|
- **Scope:** Detect crates via metadata in `.fingerprint`, Cargo.lock fragments, or embedded `rustc` markers; robust fallback to binary hash classification.
|
||||||
|
|||||||
Reference in New Issue
Block a user