more audit work
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
// <copyright file="VulnerabilityElementBuilder.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using StellaOps.Spdx3.Model;
|
||||
using StellaOps.Spdx3.Model.Security;
|
||||
|
||||
namespace StellaOps.VexLens.Spdx3;
|
||||
|
||||
/// <summary>
|
||||
/// Builds SPDX 3.0.1 Vulnerability elements from CVE and other vulnerability data.
|
||||
/// Sprint: SPRINT_20260107_004_004 Task SP-004
|
||||
/// </summary>
|
||||
public sealed class VulnerabilityElementBuilder
|
||||
{
|
||||
private readonly string _spdxIdPrefix;
|
||||
private readonly List<Spdx3ExternalIdentifier> _externalIdentifiers = new();
|
||||
private readonly List<Spdx3ExternalRef> _externalRefs = new();
|
||||
|
||||
private string? _vulnerabilityId;
|
||||
private string? _description;
|
||||
private DateTimeOffset? _publishedTime;
|
||||
private DateTimeOffset? _modifiedTime;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VulnerabilityElementBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="spdxIdPrefix">Prefix for generating SPDX IDs.</param>
|
||||
public VulnerabilityElementBuilder(string spdxIdPrefix)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(spdxIdPrefix);
|
||||
_spdxIdPrefix = spdxIdPrefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the vulnerability ID (e.g., CVE-2026-1234).
|
||||
/// </summary>
|
||||
/// <param name="vulnerabilityId">The vulnerability ID.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithVulnerabilityId(string vulnerabilityId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(vulnerabilityId);
|
||||
_vulnerabilityId = vulnerabilityId;
|
||||
|
||||
// Auto-detect identifier type and add external identifier
|
||||
if (vulnerabilityId.StartsWith("CVE-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_externalIdentifiers.Add(new Spdx3ExternalIdentifier
|
||||
{
|
||||
ExternalIdentifierType = "cve",
|
||||
Identifier = vulnerabilityId,
|
||||
IdentifierLocator = ImmutableArray.Create($"https://nvd.nist.gov/vuln/detail/{vulnerabilityId}")
|
||||
});
|
||||
}
|
||||
else if (vulnerabilityId.StartsWith("GHSA-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_externalIdentifiers.Add(new Spdx3ExternalIdentifier
|
||||
{
|
||||
ExternalIdentifierType = "ghsa",
|
||||
Identifier = vulnerabilityId,
|
||||
IdentifierLocator = ImmutableArray.Create($"https://github.com/advisories/{vulnerabilityId}")
|
||||
});
|
||||
}
|
||||
else if (vulnerabilityId.StartsWith("OSV-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_externalIdentifiers.Add(new Spdx3ExternalIdentifier
|
||||
{
|
||||
ExternalIdentifierType = "osv",
|
||||
Identifier = vulnerabilityId,
|
||||
IdentifierLocator = ImmutableArray.Create($"https://osv.dev/vulnerability/{vulnerabilityId}")
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the vulnerability description.
|
||||
/// </summary>
|
||||
/// <param name="description">The description text.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithDescription(string? description)
|
||||
{
|
||||
_description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the published time.
|
||||
/// </summary>
|
||||
/// <param name="publishedTime">When the vulnerability was published.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithPublishedTime(DateTimeOffset? publishedTime)
|
||||
{
|
||||
_publishedTime = publishedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the modified time.
|
||||
/// </summary>
|
||||
/// <param name="modifiedTime">When the vulnerability was last modified.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithModifiedTime(DateTimeOffset? modifiedTime)
|
||||
{
|
||||
_modifiedTime = modifiedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reference to NVD.
|
||||
/// </summary>
|
||||
/// <param name="cveId">The CVE ID for NVD lookup.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithNvdReference(string cveId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(cveId);
|
||||
_externalRefs.Add(new Spdx3ExternalRef
|
||||
{
|
||||
ExternalRefType = "securityAdvisory",
|
||||
Locator = ImmutableArray.Create($"https://nvd.nist.gov/vuln/detail/{cveId}")
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reference to OSV.
|
||||
/// </summary>
|
||||
/// <param name="vulnerabilityId">The vulnerability ID for OSV lookup.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithOsvReference(string vulnerabilityId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(vulnerabilityId);
|
||||
_externalRefs.Add(new Spdx3ExternalRef
|
||||
{
|
||||
ExternalRefType = "securityAdvisory",
|
||||
Locator = ImmutableArray.Create($"https://osv.dev/vulnerability/{vulnerabilityId}")
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom external reference.
|
||||
/// </summary>
|
||||
/// <param name="refType">The reference type.</param>
|
||||
/// <param name="locator">The reference URL.</param>
|
||||
/// <returns>This builder for fluent chaining.</returns>
|
||||
public VulnerabilityElementBuilder WithExternalRef(string refType, string locator)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(refType);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(locator);
|
||||
_externalRefs.Add(new Spdx3ExternalRef
|
||||
{
|
||||
ExternalRefType = refType,
|
||||
Locator = ImmutableArray.Create(locator)
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the SPDX 3.0.1 Vulnerability element.
|
||||
/// </summary>
|
||||
/// <returns>The constructed Vulnerability element.</returns>
|
||||
/// <exception cref="InvalidOperationException">If vulnerability ID is not set.</exception>
|
||||
public Spdx3Vulnerability Build()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_vulnerabilityId))
|
||||
{
|
||||
throw new InvalidOperationException("Vulnerability ID is required. Call WithVulnerabilityId() first.");
|
||||
}
|
||||
|
||||
return new Spdx3Vulnerability
|
||||
{
|
||||
SpdxId = GenerateSpdxId(),
|
||||
Type = Spdx3Vulnerability.TypeName,
|
||||
Name = _vulnerabilityId,
|
||||
Description = _description,
|
||||
PublishedTime = _publishedTime,
|
||||
ModifiedTime = _modifiedTime,
|
||||
ExternalIdentifiers = _externalIdentifiers.ToImmutableArray(),
|
||||
ExternalRefs = _externalRefs.ToImmutableArray()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vulnerability element from a CVE ID with NVD reference.
|
||||
/// </summary>
|
||||
/// <param name="cveId">The CVE ID.</param>
|
||||
/// <param name="spdxIdPrefix">Prefix for SPDX ID generation.</param>
|
||||
/// <param name="description">Optional description.</param>
|
||||
/// <returns>The constructed Vulnerability element.</returns>
|
||||
public static Spdx3Vulnerability FromCve(
|
||||
string cveId,
|
||||
string spdxIdPrefix,
|
||||
string? description = null)
|
||||
{
|
||||
return new VulnerabilityElementBuilder(spdxIdPrefix)
|
||||
.WithVulnerabilityId(cveId)
|
||||
.WithDescription(description)
|
||||
.WithNvdReference(cveId)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private string GenerateSpdxId()
|
||||
{
|
||||
// Generate a deterministic SPDX ID from the vulnerability ID
|
||||
using var sha = System.Security.Cryptography.SHA256.Create();
|
||||
var input = _vulnerabilityId ?? string.Empty;
|
||||
var hash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
|
||||
var shortHash = Convert.ToHexStringLower(hash)[..12];
|
||||
return $"{_spdxIdPrefix.TrimEnd('/')}/vulnerability/{shortHash}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 3.0.1 External Identifier.
|
||||
/// Sprint: SPRINT_20260107_004_004 Task SP-004
|
||||
/// </summary>
|
||||
public sealed record Spdx3ExternalIdentifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the external identifier type (e.g., "cve", "ghsa", "osv").
|
||||
/// </summary>
|
||||
public required string ExternalIdentifierType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier value.
|
||||
/// </summary>
|
||||
public required string Identifier { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the locator URLs for the identifier.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> IdentifierLocator { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets issuing authority of the identifier.
|
||||
/// </summary>
|
||||
public string? IssuingAuthority { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 3.0.1 External Reference.
|
||||
/// Sprint: SPRINT_20260107_004_004 Task SP-004
|
||||
/// </summary>
|
||||
public sealed record Spdx3ExternalRef
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the external reference type (e.g., "securityAdvisory").
|
||||
/// </summary>
|
||||
public required string ExternalRefType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the locator URLs.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> Locator { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content type of the referenced resource.
|
||||
/// </summary>
|
||||
public string? ContentType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a comment about the reference.
|
||||
/// </summary>
|
||||
public string? Comment { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user