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:
2025-10-23 07:57:16 +03:00
parent 224c76c276
commit 09d21d977c
17 changed files with 1857 additions and 1343 deletions

View File

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

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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. |

View File

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

View File

@@ -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"
} }
] ]
} }

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
StellaOps Toolkit License
=========================
Reusable toolkit licensing terms for analyzer fixtures.
This document is intentionally short for deterministic hashing tests.

View File

@@ -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>

View File

@@ -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",

View File

@@ -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>

View File

@@ -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"
} }
] ]
} }

View File

@@ -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>

View File

@@ -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.

View File

@@ -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>

View File

@@ -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",

View File

@@ -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.02ms; 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.