save work

This commit is contained in:
StellaOps Bot
2025-12-19 09:40:41 +02:00
parent 2eafe98d44
commit 43882078a4
44 changed files with 3044 additions and 492 deletions

View File

@@ -1,4 +1,7 @@
using System.Collections.Immutable;
using System.Globalization;
using StellaOps.Scanner.Analyzers.Native.Index;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Emit.Native;
@@ -17,7 +20,143 @@ public sealed record NativeComponentEmitResult(
string? Version,
NativeBinaryMetadata Metadata,
bool IndexMatch,
BuildIdLookupResult? LookupResult);
BuildIdLookupResult? LookupResult)
{
public ComponentRecord ToComponentRecord(string layerDigest)
{
ArgumentException.ThrowIfNullOrWhiteSpace(layerDigest);
ArgumentNullException.ThrowIfNull(Metadata);
var fileName = string.IsNullOrWhiteSpace(Name)
? Path.GetFileName(Metadata.FilePath)
: Name.Trim();
if (string.IsNullOrWhiteSpace(fileName))
{
fileName = Purl;
}
var properties = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
["stellaops:binary.format"] = Metadata.Format,
["stellaops:binary.indexMatch"] = IndexMatch ? "true" : "false",
};
AddIfNotEmpty(properties, "stellaops:binary.architecture", Metadata.Architecture);
AddIfNotEmpty(properties, "stellaops:binary.platform", Metadata.Platform);
AddIfNotEmpty(properties, "stellaops:binary.filePath", Metadata.FilePath);
AddIfNotEmpty(properties, "stellaops:binary.fileDigest", Metadata.FileDigest);
if (Metadata.FileSize > 0)
{
properties["stellaops:binary.fileSizeBytes"] = Metadata.FileSize.ToString(CultureInfo.InvariantCulture);
}
if (Metadata.LayerIndex >= 0)
{
properties["stellaops:binary.layerIndex"] = Metadata.LayerIndex.ToString(CultureInfo.InvariantCulture);
}
if (Metadata.Is64Bit)
{
properties["stellaops:binary.is64Bit"] = "true";
}
if (Metadata.IsSigned)
{
properties["stellaops:binary.isSigned"] = "true";
}
AddIfNotEmpty(properties, "stellaops:binary.signatureDetails", Metadata.SignatureDetails);
AddIfNotEmpty(properties, "stellaops:binary.productVersion", Metadata.ProductVersion);
AddIfNotEmpty(properties, "stellaops:binary.fileVersion", Metadata.FileVersion);
AddIfNotEmpty(properties, "stellaops:binary.companyName", Metadata.CompanyName);
AddDictionary(properties, "stellaops:binary.hardeningFlags", Metadata.HardeningFlags);
AddList(properties, "stellaops:binary.imports", Metadata.Imports);
AddList(properties, "stellaops:binary.exports", Metadata.Exports);
if (LookupResult is not null)
{
AddIfNotEmpty(properties, "stellaops:binary.index.sourceDistro", LookupResult.SourceDistro);
properties["stellaops:binary.index.confidence"] = LookupResult.Confidence.ToString();
}
var componentMetadata = new ComponentMetadata
{
BuildId = Metadata.BuildId,
Properties = properties.Count == 0 ? null : properties,
};
return new ComponentRecord
{
Identity = ComponentIdentity.Create(
key: Purl,
name: fileName,
version: Version,
purl: Purl,
componentType: "file"),
LayerDigest = layerDigest,
Evidence = ImmutableArray.Create(ComponentEvidence.FromPath(Metadata.FilePath)),
Dependencies = ImmutableArray<string>.Empty,
Metadata = componentMetadata,
Usage = ComponentUsage.Unused,
};
}
private static void AddIfNotEmpty(IDictionary<string, string> properties, string key, string? value)
{
if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value))
{
return;
}
properties[key] = value.Trim();
}
private static void AddDictionary(IDictionary<string, string> properties, string key, IReadOnlyDictionary<string, string>? dictionary)
{
if (dictionary is null || dictionary.Count == 0)
{
return;
}
var entries = dictionary
.Where(pair => !string.IsNullOrWhiteSpace(pair.Key) && !string.IsNullOrWhiteSpace(pair.Value))
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => $"{pair.Key}={pair.Value}")
.ToArray();
if (entries.Length == 0)
{
return;
}
properties[key] = string.Join(",", entries);
}
private static void AddList(IDictionary<string, string> properties, string key, IReadOnlyList<string>? items)
{
if (items is null || items.Count == 0)
{
return;
}
var normalized = items
.Where(static item => !string.IsNullOrWhiteSpace(item))
.Select(static item => item.Trim())
.Distinct(StringComparer.Ordinal)
.OrderBy(static item => item, StringComparer.Ordinal)
.ToArray();
if (normalized.Length == 0)
{
return;
}
properties[key] = string.Join(",", normalized);
}
}
/// <summary>
/// Interface for emitting native binary components for SBOM generation.

View File

@@ -6,6 +6,7 @@
// -----------------------------------------------------------------------------
using StellaOps.Scanner.Analyzers.Native.Index;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Emit.Native;
@@ -183,7 +184,15 @@ public sealed record LayerComponentMapping(
IReadOnlyList<NativeComponentEmitResult> Components,
int TotalCount,
int ResolvedCount,
int UnresolvedCount);
int UnresolvedCount)
{
public LayerComponentFragment ToFragment()
{
return LayerComponentFragment.Create(
LayerDigest,
Components.Select(component => component.ToComponentRecord(LayerDigest)));
}
}
/// <summary>
/// Result of mapping an entire container image to SBOM components.

View File

@@ -0,0 +1,5 @@
# Scanner Emit Local Tasks
| Task ID | Sprint | Status | Notes |
| --- | --- | --- | --- |
| `BSE-009` | `docs/implplan/SPRINT_3500_0012_0001_binary_sbom_emission.md` | DONE | Added end-to-end integration test coverage for native binary SBOM emission (emit → fragments → CycloneDX). |