// // Copyright (c) StellaOps. Licensed under the BUSL-1.1. // using System.Collections.Immutable; using StellaOps.Spdx3.Model; using StellaOps.Spdx3.Model.Security; namespace StellaOps.VexLens.Spdx3; /// /// Maps VEX consensus results to SPDX 3.0.1 Security profile documents. /// Sprint: SPRINT_20260107_004_004 Task SP-006 /// public sealed class VexToSpdx3Mapper : IVexToSpdx3Mapper { private readonly TimeProvider _timeProvider; /// /// Initializes a new instance of the class. /// /// Time provider for timestamps. public VexToSpdx3Mapper(TimeProvider timeProvider) { _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); } /// /// Initializes a new instance using the system time provider. /// public VexToSpdx3Mapper() : this(TimeProvider.System) { } /// public Task 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.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); } /// public VexMappingResult MapStatements( IEnumerable statements, string spdxIdPrefix) { ArgumentNullException.ThrowIfNull(statements); ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix); var vulnerabilities = new List(); var assessments = new List(); var seenVulnerabilities = new HashSet(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 FilterStatements( IReadOnlyList statements, VexToSpdx3Options options) { IEnumerable result = statements; if (options.ProductFilter is { Count: > 0 }) { var productSet = new HashSet(options.ProductFilter, StringComparer.OrdinalIgnoreCase); result = result.Where(s => productSet.Contains(s.ProductId)); } if (options.VulnerabilityFilter is { Count: > 0 }) { var vulnSet = new HashSet(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 BuildCvssAssessments( IEnumerable statements, string spdxIdPrefix) { foreach (var statement in statements.Where(s => s.CvssV3 is not null)) { var cvss = CvssMapper.MapToSpdx3( statement.CvssV3!, statement.VulnerabilityId, statement.ProductId, spdxIdPrefix); yield return cvss; } } private static IEnumerable BuildEpssAssessments( IEnumerable statements, string spdxIdPrefix) { foreach (var statement in statements.Where(s => s.Epss is not null)) { var epss = CvssMapper.MapEpssToSpdx3( statement.Epss!, statement.VulnerabilityId, statement.ProductId, spdxIdPrefix); yield return epss; } } }