more audit work
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
// <copyright file="VexToSpdx3Mapper.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Spdx3.Model;
|
||||
using StellaOps.Spdx3.Model.Security;
|
||||
|
||||
namespace StellaOps.VexLens.Spdx3;
|
||||
|
||||
/// <summary>
|
||||
/// Maps VEX consensus results to SPDX 3.0.1 Security profile documents.
|
||||
/// Sprint: SPRINT_20260107_004_004 Task SP-006
|
||||
/// </summary>
|
||||
public sealed class VexToSpdx3Mapper : IVexToSpdx3Mapper
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VexToSpdx3Mapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Time provider for timestamps.</param>
|
||||
public VexToSpdx3Mapper(TimeProvider timeProvider)
|
||||
{
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance using the system time provider.
|
||||
/// </summary>
|
||||
public VexToSpdx3Mapper() : this(TimeProvider.System)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Spdx3Document> MapConsensusAsync(
|
||||
VexConsensus consensus,
|
||||
VexToSpdx3Options options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(consensus);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
// Filter statements if filters are specified
|
||||
var statements = FilterStatements(consensus.Statements, options);
|
||||
|
||||
// Map statements to SPDX 3.0.1 elements
|
||||
var mappingResult = MapStatements(statements, options.SpdxIdPrefix);
|
||||
|
||||
// Build creation info
|
||||
var creationInfo = new Spdx3CreationInfo
|
||||
{
|
||||
Id = $"{options.SpdxIdPrefix}/creationInfo/{consensus.DocumentId}",
|
||||
SpecVersion = Spdx3CreationInfo.Spdx301Version,
|
||||
Created = consensus.Timestamp ?? _timeProvider.GetUtcNow(),
|
||||
CreatedBy = consensus.Author is not null
|
||||
? ImmutableArray.Create(consensus.Author)
|
||||
: ImmutableArray<string>.Empty,
|
||||
CreatedUsing = ImmutableArray.Create(options.ToolId),
|
||||
Profile = ImmutableArray.Create(
|
||||
Spdx3ProfileIdentifier.Core,
|
||||
Spdx3ProfileIdentifier.Security),
|
||||
DataLicense = Spdx3CreationInfo.Spdx301DataLicense
|
||||
};
|
||||
|
||||
// Include CVSS assessments if requested
|
||||
var allElements = mappingResult.AllElements.ToList();
|
||||
|
||||
if (options.IncludeCvss)
|
||||
{
|
||||
var cvssAssessments = BuildCvssAssessments(statements, options.SpdxIdPrefix);
|
||||
allElements.AddRange(cvssAssessments);
|
||||
}
|
||||
|
||||
if (options.IncludeEpss)
|
||||
{
|
||||
var epssAssessments = BuildEpssAssessments(statements, options.SpdxIdPrefix);
|
||||
allElements.AddRange(epssAssessments);
|
||||
}
|
||||
|
||||
var document = new Spdx3Document(
|
||||
elements: allElements,
|
||||
creationInfos: new[] { creationInfo },
|
||||
profiles: new[] { Spdx3ProfileIdentifier.Core, Spdx3ProfileIdentifier.Security });
|
||||
|
||||
return Task.FromResult(document);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VexMappingResult MapStatements(
|
||||
IEnumerable<OpenVexStatement> statements,
|
||||
string spdxIdPrefix)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(statements);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix);
|
||||
|
||||
var vulnerabilities = new List<Spdx3Element>();
|
||||
var assessments = new List<Spdx3Element>();
|
||||
var seenVulnerabilities = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var statement in statements)
|
||||
{
|
||||
// Create vulnerability element if not already created
|
||||
if (!seenVulnerabilities.Contains(statement.VulnerabilityId))
|
||||
{
|
||||
var vulnerability = BuildVulnerability(statement, spdxIdPrefix);
|
||||
vulnerabilities.Add(vulnerability);
|
||||
seenVulnerabilities.Add(statement.VulnerabilityId);
|
||||
}
|
||||
|
||||
// Create VEX assessment relationship
|
||||
var assessment = VexStatusMapper.MapToSpdx3(statement, spdxIdPrefix);
|
||||
assessments.Add(assessment);
|
||||
}
|
||||
|
||||
return new VexMappingResult
|
||||
{
|
||||
Vulnerabilities = vulnerabilities,
|
||||
Assessments = assessments
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenVexStatement> FilterStatements(
|
||||
IReadOnlyList<OpenVexStatement> statements,
|
||||
VexToSpdx3Options options)
|
||||
{
|
||||
IEnumerable<OpenVexStatement> result = statements;
|
||||
|
||||
if (options.ProductFilter is { Count: > 0 })
|
||||
{
|
||||
var productSet = new HashSet<string>(options.ProductFilter, StringComparer.OrdinalIgnoreCase);
|
||||
result = result.Where(s => productSet.Contains(s.ProductId));
|
||||
}
|
||||
|
||||
if (options.VulnerabilityFilter is { Count: > 0 })
|
||||
{
|
||||
var vulnSet = new HashSet<string>(options.VulnerabilityFilter, StringComparer.OrdinalIgnoreCase);
|
||||
result = result.Where(s => vulnSet.Contains(s.VulnerabilityId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Spdx3Vulnerability BuildVulnerability(OpenVexStatement statement, string spdxIdPrefix)
|
||||
{
|
||||
return new VulnerabilityElementBuilder(spdxIdPrefix)
|
||||
.WithVulnerabilityId(statement.VulnerabilityId)
|
||||
.WithPublishedTime(statement.Timestamp)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static IEnumerable<Spdx3Element> BuildCvssAssessments(
|
||||
IEnumerable<OpenVexStatement> statements,
|
||||
string spdxIdPrefix)
|
||||
{
|
||||
foreach (var statement in statements.Where(s => s.CvssV3 is not null))
|
||||
{
|
||||
var cvss = CvssMapper.MapToSpdx3(
|
||||
statement.VulnerabilityId,
|
||||
statement.ProductId,
|
||||
statement.CvssV3!,
|
||||
spdxIdPrefix);
|
||||
|
||||
yield return cvss;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Spdx3Element> BuildEpssAssessments(
|
||||
IEnumerable<OpenVexStatement> statements,
|
||||
string spdxIdPrefix)
|
||||
{
|
||||
foreach (var statement in statements.Where(s => s.Epss is not null))
|
||||
{
|
||||
var epss = CvssMapper.MapEpssToSpdx3(
|
||||
statement.VulnerabilityId,
|
||||
statement.ProductId,
|
||||
statement.Epss!,
|
||||
spdxIdPrefix);
|
||||
|
||||
yield return epss;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user