Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Native/NativeComponentEmitter.cs
master 00d2c99af9 feat: add Attestation Chain and Triage Evidence API clients and models
- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains.
- Created models for Attestation Chain, including DSSE envelope structures and verification results.
- Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component.
- Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence.
- Introduced mock implementations for both API clients to facilitate testing and development.
2025-12-18 13:15:13 +02:00

156 lines
4.9 KiB
C#

using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Analyzers.Native.Index;
namespace StellaOps.Scanner.Emit.Native;
/// <summary>
/// Emits native binary components for SBOM generation.
/// Uses the Build-ID index to resolve PURLs when possible.
/// </summary>
public sealed class NativeComponentEmitter : INativeComponentEmitter
{
private readonly IBuildIdIndex _buildIdIndex;
private readonly NativePurlBuilder _purlBuilder;
private readonly ILogger<NativeComponentEmitter> _logger;
/// <summary>
/// Creates a new native component emitter.
/// </summary>
public NativeComponentEmitter(
IBuildIdIndex buildIdIndex,
ILogger<NativeComponentEmitter> logger)
{
ArgumentNullException.ThrowIfNull(buildIdIndex);
ArgumentNullException.ThrowIfNull(logger);
_buildIdIndex = buildIdIndex;
_purlBuilder = new NativePurlBuilder();
_logger = logger;
}
/// <inheritdoc />
public async Task<NativeComponentEmitResult> EmitAsync(
NativeBinaryMetadata metadata,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(metadata);
// Try to resolve via Build-ID index
BuildIdLookupResult? lookupResult = null;
if (!string.IsNullOrWhiteSpace(metadata.BuildId))
{
lookupResult = await _buildIdIndex.LookupAsync(metadata.BuildId, cancellationToken).ConfigureAwait(false);
}
string purl;
string? version = null;
bool indexMatch = false;
if (lookupResult is not null)
{
// Index match - use the resolved PURL
purl = _purlBuilder.FromIndexResult(lookupResult);
version = lookupResult.Version;
indexMatch = true;
_logger.LogDebug(
"Resolved binary {FilePath} via Build-ID index: {Purl}",
metadata.FilePath,
purl);
}
else
{
// No match - generate generic PURL
purl = _purlBuilder.FromUnresolvedBinary(metadata);
version = metadata.ProductVersion ?? metadata.FileVersion;
_logger.LogDebug(
"Unresolved binary {FilePath}, generated generic PURL: {Purl}",
metadata.FilePath,
purl);
}
var name = Path.GetFileName(metadata.FilePath);
return new NativeComponentEmitResult(
Purl: purl,
Name: name,
Version: version,
Metadata: metadata,
IndexMatch: indexMatch,
LookupResult: lookupResult);
}
/// <inheritdoc />
public async Task<IReadOnlyList<NativeComponentEmitResult>> EmitBatchAsync(
IEnumerable<NativeBinaryMetadata> metadataList,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(metadataList);
var metadataArray = metadataList.ToArray();
if (metadataArray.Length == 0)
{
return Array.Empty<NativeComponentEmitResult>();
}
// Batch lookup for all Build-IDs
var buildIds = metadataArray
.Where(m => !string.IsNullOrWhiteSpace(m.BuildId))
.Select(m => m.BuildId!)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
var lookupResults = await _buildIdIndex.BatchLookupAsync(buildIds, cancellationToken).ConfigureAwait(false);
var lookupMap = lookupResults.ToDictionary(
r => r.BuildId,
StringComparer.OrdinalIgnoreCase);
_logger.LogDebug(
"Batch lookup: {Total} binaries, {Resolved} resolved via index",
metadataArray.Length,
lookupMap.Count);
// Emit components
var results = new List<NativeComponentEmitResult>(metadataArray.Length);
foreach (var metadata in metadataArray)
{
BuildIdLookupResult? lookupResult = null;
if (!string.IsNullOrWhiteSpace(metadata.BuildId) &&
lookupMap.TryGetValue(metadata.BuildId, out var result))
{
lookupResult = result;
}
string purl;
string? version = null;
bool indexMatch = false;
if (lookupResult is not null)
{
purl = _purlBuilder.FromIndexResult(lookupResult);
version = lookupResult.Version;
indexMatch = true;
}
else
{
purl = _purlBuilder.FromUnresolvedBinary(metadata);
version = metadata.ProductVersion ?? metadata.FileVersion;
}
results.Add(new NativeComponentEmitResult(
Purl: purl,
Name: Path.GetFileName(metadata.FilePath),
Version: version,
Metadata: metadata,
IndexMatch: indexMatch,
LookupResult: lookupResult));
}
return results;
}
}