license switch agpl -> busl1, sprints work, new product advisories
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraConnector.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraConnectorPlugin.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraTrustDefaults.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Astra;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraOptions.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -307,4 +307,4 @@ sha256sum astra-linux-1.7-oval.xml
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ParsedSbom.cs
|
||||
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
|
||||
// Task: TASK-015-001 - Parsed SBOM model
|
||||
// Description: Enriched SBOM extraction model for downstream consumers
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Concelier.SbomIntegration.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Enriched SBOM extraction result.
|
||||
/// </summary>
|
||||
public sealed record ParsedSbom
|
||||
{
|
||||
public required string Format { get; init; }
|
||||
public required string SpecVersion { get; init; }
|
||||
public required string SerialNumber { get; init; }
|
||||
public ImmutableArray<ParsedComponent> Components { get; init; } = [];
|
||||
public ImmutableArray<ParsedService> Services { get; init; } = [];
|
||||
public ImmutableArray<ParsedDependency> Dependencies { get; init; } = [];
|
||||
public ImmutableArray<ParsedComposition> Compositions { get; init; } = [];
|
||||
public ImmutableArray<ParsedVulnerability> Vulnerabilities { get; init; } = [];
|
||||
public ParsedFormulation? Formulation { get; init; }
|
||||
public ParsedBuildInfo? BuildInfo { get; init; }
|
||||
public ParsedDeclarations? Declarations { get; init; }
|
||||
public ParsedDefinitions? Definitions { get; init; }
|
||||
public ImmutableArray<ParsedAnnotation> Annotations { get; init; } = [];
|
||||
public required ParsedSbomMetadata Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata extracted from SBOM headers.
|
||||
/// </summary>
|
||||
public sealed record ParsedSbomMetadata
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public DateTimeOffset? Timestamp { get; init; }
|
||||
public ImmutableArray<string> Tools { get; init; } = [];
|
||||
public ImmutableArray<string> Authors { get; init; } = [];
|
||||
public string? Supplier { get; init; }
|
||||
public string? Manufacturer { get; init; }
|
||||
public ImmutableArray<string> Profiles { get; init; } = [];
|
||||
public ImmutableArray<ParsedNamespaceMapEntry> NamespaceMap { get; init; } = [];
|
||||
public ImmutableArray<string> Imports { get; init; } = [];
|
||||
public string? RootComponentRef { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedNamespaceMapEntry
|
||||
{
|
||||
public required string Prefix { get; init; }
|
||||
public required string Namespace { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Software component extracted from an SBOM.
|
||||
/// </summary>
|
||||
public sealed record ParsedComponent
|
||||
{
|
||||
public required string BomRef { get; init; }
|
||||
public string? Type { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Purl { get; init; }
|
||||
public string? Cpe { get; init; }
|
||||
public string? Group { get; init; }
|
||||
public string? Publisher { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
|
||||
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
|
||||
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
public ParsedEvidence? Evidence { get; init; }
|
||||
public ParsedPedigree? Pedigree { get; init; }
|
||||
public ParsedCryptoProperties? CryptoProperties { get; init; }
|
||||
public ParsedModelCard? ModelCard { get; init; }
|
||||
public ParsedOrganization? Supplier { get; init; }
|
||||
public ParsedOrganization? Manufacturer { get; init; }
|
||||
public ComponentScope Scope { get; init; } = ComponentScope.Required;
|
||||
public bool Modified { get; init; }
|
||||
}
|
||||
|
||||
public enum ComponentScope
|
||||
{
|
||||
Required,
|
||||
Optional,
|
||||
Excluded,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public sealed record ParsedHash
|
||||
{
|
||||
public required string Algorithm { get; init; }
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedExternalRef
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Url { get; init; }
|
||||
public string? Comment { get; init; }
|
||||
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedOrganization
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Url { get; init; }
|
||||
public string? Contact { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedEvidence
|
||||
{
|
||||
public ParsedEvidenceIdentity? Identity { get; init; }
|
||||
public ImmutableArray<ParsedEvidenceOccurrence> Occurrences { get; init; } = [];
|
||||
public ParsedEvidenceCallstack? Callstack { get; init; }
|
||||
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
|
||||
public ImmutableArray<string> Copyrights { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedEvidenceIdentity
|
||||
{
|
||||
public string? Field { get; init; }
|
||||
public double? Confidence { get; init; }
|
||||
public string? Value { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedEvidenceOccurrence
|
||||
{
|
||||
public string? Location { get; init; }
|
||||
public int? Line { get; init; }
|
||||
public int? Offset { get; init; }
|
||||
public string? Symbol { get; init; }
|
||||
public string? AdditionalContext { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedEvidenceCallstack
|
||||
{
|
||||
public ImmutableArray<ParsedCallstackFrame> Frames { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedCallstackFrame
|
||||
{
|
||||
public string? Package { get; init; }
|
||||
public string? Module { get; init; }
|
||||
public string? Function { get; init; }
|
||||
public ImmutableArray<string> Parameters { get; init; } = [];
|
||||
public int? Line { get; init; }
|
||||
public int? Column { get; init; }
|
||||
public string? FullFilename { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedPedigree
|
||||
{
|
||||
public ImmutableArray<ParsedComponentReference> Ancestors { get; init; } = [];
|
||||
public ImmutableArray<ParsedComponentReference> Variants { get; init; } = [];
|
||||
public ImmutableArray<ParsedComponentReference> Commits { get; init; } = [];
|
||||
public ImmutableArray<ParsedPatch> Patches { get; init; } = [];
|
||||
public ImmutableArray<string> Notes { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedComponentReference
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedPatch
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Diff { get; init; }
|
||||
public string? Url { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedService
|
||||
{
|
||||
public required string BomRef { get; init; }
|
||||
public string? Provider { get; init; }
|
||||
public string? Group { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ImmutableArray<string> Endpoints { get; init; } = [];
|
||||
public bool Authenticated { get; init; }
|
||||
public bool CrossesTrustBoundary { get; init; }
|
||||
public ImmutableArray<ParsedDataFlow> Data { get; init; } = [];
|
||||
public ImmutableArray<ParsedLicense> Licenses { get; init; } = [];
|
||||
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
|
||||
public ImmutableArray<ParsedService> NestedServices { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedDataFlow
|
||||
{
|
||||
public DataFlowDirection Direction { get; init; }
|
||||
public string? Classification { get; init; }
|
||||
public string? SourceRef { get; init; }
|
||||
public string? DestinationRef { get; init; }
|
||||
}
|
||||
|
||||
public enum DataFlowDirection
|
||||
{
|
||||
Unknown,
|
||||
Inbound,
|
||||
Outbound,
|
||||
Bidirectional
|
||||
}
|
||||
|
||||
public sealed record ParsedDependency
|
||||
{
|
||||
public required string SourceRef { get; init; }
|
||||
public ImmutableArray<string> DependsOn { get; init; } = [];
|
||||
public DependencyScope Scope { get; init; } = DependencyScope.Runtime;
|
||||
}
|
||||
|
||||
public enum DependencyScope
|
||||
{
|
||||
Runtime,
|
||||
Development,
|
||||
Optional,
|
||||
Test,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public sealed record ParsedComposition
|
||||
{
|
||||
public CompositionAggregate Aggregate { get; init; } = CompositionAggregate.Unknown;
|
||||
public ImmutableArray<string> Assemblies { get; init; } = [];
|
||||
public ImmutableArray<string> Dependencies { get; init; } = [];
|
||||
public ImmutableArray<string> Vulnerabilities { get; init; } = [];
|
||||
}
|
||||
|
||||
public enum CompositionAggregate
|
||||
{
|
||||
Complete,
|
||||
Incomplete,
|
||||
IncompleteFirstPartyProprietary,
|
||||
IncompleteFirstPartyOpenSource,
|
||||
IncompleteThirdPartyProprietary,
|
||||
IncompleteThirdPartyOpenSource,
|
||||
Unknown,
|
||||
NotSpecified
|
||||
}
|
||||
|
||||
public sealed record ParsedAnnotation
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public ImmutableArray<string> Subjects { get; init; } = [];
|
||||
public ParsedAnnotator? Annotator { get; init; }
|
||||
public DateTimeOffset? Timestamp { get; init; }
|
||||
public string? Text { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedAnnotator
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public string? Reference { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedFormulation
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public ImmutableArray<ParsedFormula> Components { get; init; } = [];
|
||||
public ImmutableArray<ParsedWorkflow> Workflows { get; init; } = [];
|
||||
public ImmutableArray<ParsedTask> Tasks { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedFormula
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public ImmutableArray<string> ComponentRefs { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedWorkflow
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ImmutableArray<string> InputRefs { get; init; } = [];
|
||||
public ImmutableArray<string> OutputRefs { get; init; } = [];
|
||||
public ImmutableArray<ParsedTask> Tasks { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedTask
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ImmutableArray<string> InputRefs { get; init; } = [];
|
||||
public ImmutableArray<string> OutputRefs { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Parameters { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedBuildInfo
|
||||
{
|
||||
public required string BuildId { get; init; }
|
||||
public string? BuildType { get; init; }
|
||||
public DateTimeOffset? BuildStartTime { get; init; }
|
||||
public DateTimeOffset? BuildEndTime { get; init; }
|
||||
public string? ConfigSourceEntrypoint { get; init; }
|
||||
public string? ConfigSourceDigest { get; init; }
|
||||
public string? ConfigSourceUri { get; init; }
|
||||
public ImmutableDictionary<string, string> Environment { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
public ImmutableDictionary<string, string> Parameters { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedCryptoProperties
|
||||
{
|
||||
public CryptoAssetType AssetType { get; init; }
|
||||
public ParsedAlgorithmProperties? AlgorithmProperties { get; init; }
|
||||
public ParsedCertificateProperties? CertificateProperties { get; init; }
|
||||
public ParsedProtocolProperties? ProtocolProperties { get; init; }
|
||||
public ParsedRelatedCryptoMaterial? RelatedCryptoMaterial { get; init; }
|
||||
public string? Oid { get; init; }
|
||||
}
|
||||
|
||||
public enum CryptoAssetType
|
||||
{
|
||||
Algorithm,
|
||||
Certificate,
|
||||
Protocol,
|
||||
RelatedCryptoMaterial,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public sealed record ParsedAlgorithmProperties
|
||||
{
|
||||
public CryptoPrimitive? Primitive { get; init; }
|
||||
public string? ParameterSetIdentifier { get; init; }
|
||||
public string? Curve { get; init; }
|
||||
public CryptoExecutionEnvironment? ExecutionEnvironment { get; init; }
|
||||
public string? ImplementationPlatform { get; init; }
|
||||
public CertificationLevel? CertificationLevel { get; init; }
|
||||
public CryptoMode? Mode { get; init; }
|
||||
public CryptoPadding? Padding { get; init; }
|
||||
public ImmutableArray<string> CryptoFunctions { get; init; } = [];
|
||||
public int? ClassicalSecurityLevel { get; init; }
|
||||
public int? NistQuantumSecurityLevel { get; init; }
|
||||
public int? KeySize { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedCertificateProperties
|
||||
{
|
||||
public string? SubjectName { get; init; }
|
||||
public string? IssuerName { get; init; }
|
||||
public DateTimeOffset? NotValidBefore { get; init; }
|
||||
public DateTimeOffset? NotValidAfter { get; init; }
|
||||
public string? SignatureAlgorithmRef { get; init; }
|
||||
public string? SubjectPublicKeyRef { get; init; }
|
||||
public string? CertificateFormat { get; init; }
|
||||
public string? CertificateExtension { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedProtocolProperties
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public ImmutableArray<string> CipherSuites { get; init; } = [];
|
||||
public ImmutableArray<string> IkeV2TransformTypes { get; init; } = [];
|
||||
public ImmutableArray<string> CryptoRefArray { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedRelatedCryptoMaterial
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Reference { get; init; }
|
||||
public ImmutableArray<string> MaterialRefs { get; init; } = [];
|
||||
}
|
||||
|
||||
public enum CryptoPrimitive
|
||||
{
|
||||
Unknown,
|
||||
Symmetric,
|
||||
Asymmetric,
|
||||
Hash,
|
||||
Mac,
|
||||
Kdf,
|
||||
Rng
|
||||
}
|
||||
|
||||
public enum CryptoMode
|
||||
{
|
||||
Unknown,
|
||||
Ecb,
|
||||
Cbc,
|
||||
Ctr,
|
||||
Gcm,
|
||||
Xts
|
||||
}
|
||||
|
||||
public enum CryptoPadding
|
||||
{
|
||||
Unknown,
|
||||
None,
|
||||
Pkcs1,
|
||||
Pkcs7,
|
||||
Oaep
|
||||
}
|
||||
|
||||
public enum CryptoExecutionEnvironment
|
||||
{
|
||||
Unknown,
|
||||
Hardware,
|
||||
Software,
|
||||
Hybrid
|
||||
}
|
||||
|
||||
public enum CertificationLevel
|
||||
{
|
||||
Unknown,
|
||||
Fips140_2,
|
||||
Fips140_3,
|
||||
CommonCriteria
|
||||
}
|
||||
|
||||
public sealed record ParsedModelCard
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public ParsedModelParameters? ModelParameters { get; init; }
|
||||
public ParsedQuantitativeAnalysis? QuantitativeAnalysis { get; init; }
|
||||
public ParsedConsiderations? Considerations { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedModelParameters
|
||||
{
|
||||
public string? Task { get; init; }
|
||||
public string? ArchitectureFamily { get; init; }
|
||||
public string? ModelArchitecture { get; init; }
|
||||
public ImmutableArray<ParsedDatasetRef> Datasets { get; init; } = [];
|
||||
public ImmutableArray<ParsedInputOutput> Inputs { get; init; } = [];
|
||||
public ImmutableArray<ParsedInputOutput> Outputs { get; init; } = [];
|
||||
public string? AutonomyType { get; init; }
|
||||
public string? Domain { get; init; }
|
||||
public string? TypeOfModel { get; init; }
|
||||
public string? EnergyConsumption { get; init; }
|
||||
public ImmutableDictionary<string, string> Hyperparameters { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedDatasetRef
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Url { get; init; }
|
||||
public ImmutableArray<ParsedHash> Hashes { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedInputOutput
|
||||
{
|
||||
public string? Format { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedQuantitativeAnalysis
|
||||
{
|
||||
public ImmutableArray<ParsedPerformanceMetric> PerformanceMetrics { get; init; } = [];
|
||||
public ImmutableArray<ParsedGraphic> Graphics { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedPerformanceMetric
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public string? Value { get; init; }
|
||||
public string? Slice { get; init; }
|
||||
public string? ConfidenceIntervalLower { get; init; }
|
||||
public string? ConfidenceIntervalUpper { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedGraphic
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? Image { get; init; }
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedConsiderations
|
||||
{
|
||||
public ImmutableArray<string> Users { get; init; } = [];
|
||||
public ImmutableArray<string> UseCases { get; init; } = [];
|
||||
public ImmutableArray<string> TechnicalLimitations { get; init; } = [];
|
||||
public ImmutableArray<ParsedRisk> EthicalConsiderations { get; init; } = [];
|
||||
public ImmutableArray<ParsedFairnessAssessment> FairnessAssessments { get; init; } = [];
|
||||
public ParsedEnvironmentalConsiderations? EnvironmentalConsiderations { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedFairnessAssessment
|
||||
{
|
||||
public string? GroupAtRisk { get; init; }
|
||||
public string? Benefits { get; init; }
|
||||
public string? Harms { get; init; }
|
||||
public string? MitigationStrategy { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedRisk
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? MitigationStrategy { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedEnvironmentalConsiderations
|
||||
{
|
||||
public ImmutableArray<ParsedEnergyConsumption> EnergyConsumptions { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedEnergyConsumption
|
||||
{
|
||||
public string? Activity { get; init; }
|
||||
public ImmutableArray<ParsedEnergyProvider> EnergyProviders { get; init; } = [];
|
||||
public string? ActivityEnergyCost { get; init; }
|
||||
public string? Co2CostEquivalent { get; init; }
|
||||
public string? Co2CostOffset { get; init; }
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed record ParsedEnergyProvider
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ParsedOrganization? Organization { get; init; }
|
||||
public string? EnergySource { get; init; }
|
||||
public string? EnergyProvided { get; init; }
|
||||
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedVulnerability
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public string? Source { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string? Detail { get; init; }
|
||||
public string? Recommendation { get; init; }
|
||||
public ImmutableArray<string> Cwes { get; init; } = [];
|
||||
public ImmutableArray<ParsedVulnRating> Ratings { get; init; } = [];
|
||||
public ImmutableArray<ParsedVulnAffects> Affects { get; init; } = [];
|
||||
public ParsedVulnAnalysis? Analysis { get; init; }
|
||||
public DateTimeOffset? Published { get; init; }
|
||||
public DateTimeOffset? Updated { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedVulnRating
|
||||
{
|
||||
public string? Method { get; init; }
|
||||
public string? Score { get; init; }
|
||||
public string? Severity { get; init; }
|
||||
public string? Vector { get; init; }
|
||||
public string? Source { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedVulnAffects
|
||||
{
|
||||
public string? Ref { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Status { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedVulnAnalysis
|
||||
{
|
||||
public VexState State { get; init; }
|
||||
public VexJustification? Justification { get; init; }
|
||||
public ImmutableArray<string> Response { get; init; } = [];
|
||||
public string? Detail { get; init; }
|
||||
public DateTimeOffset? FirstIssued { get; init; }
|
||||
public DateTimeOffset? LastUpdated { get; init; }
|
||||
}
|
||||
|
||||
public enum VexState
|
||||
{
|
||||
Exploitable,
|
||||
InTriage,
|
||||
FalsePositive,
|
||||
NotAffected,
|
||||
Fixed,
|
||||
UnderInvestigation,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum VexJustification
|
||||
{
|
||||
ComponentNotPresent,
|
||||
VulnerableCodeNotPresent,
|
||||
VulnerableCodeNotInExecutePath,
|
||||
InlineMitigationsAlreadyExist,
|
||||
Other
|
||||
}
|
||||
|
||||
public sealed record ParsedLicense
|
||||
{
|
||||
public string? SpdxId { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public string? Url { get; init; }
|
||||
public string? Text { get; init; }
|
||||
public ParsedLicenseExpression? Expression { get; init; }
|
||||
public ParsedLicenseTerms? Licensing { get; init; }
|
||||
public ImmutableArray<string> Acknowledgements { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedLicenseTerms
|
||||
{
|
||||
public string? Licensor { get; init; }
|
||||
public string? Licensee { get; init; }
|
||||
public string? Purchaser { get; init; }
|
||||
public string? PurchaseOrder { get; init; }
|
||||
public ImmutableArray<string> LicenseTypes { get; init; } = [];
|
||||
public DateTimeOffset? LastRenewal { get; init; }
|
||||
public DateTimeOffset? Expiration { get; init; }
|
||||
public ImmutableArray<string> AltIds { get; init; } = [];
|
||||
public ImmutableDictionary<string, string> Properties { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public abstract record ParsedLicenseExpression;
|
||||
|
||||
public sealed record SimpleLicense(string Id) : ParsedLicenseExpression;
|
||||
|
||||
public sealed record WithException(ParsedLicenseExpression License, string Exception) : ParsedLicenseExpression;
|
||||
|
||||
public sealed record OrLater(string LicenseId) : ParsedLicenseExpression;
|
||||
|
||||
public sealed record ConjunctiveSet(ImmutableArray<ParsedLicenseExpression> Members) : ParsedLicenseExpression;
|
||||
|
||||
public sealed record DisjunctiveSet(ImmutableArray<ParsedLicenseExpression> Members) : ParsedLicenseExpression;
|
||||
|
||||
public enum LicenseCategory
|
||||
{
|
||||
Permissive,
|
||||
WeakCopyleft,
|
||||
StrongCopyleft,
|
||||
Proprietary,
|
||||
PublicDomain,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public sealed record ParsedDeclarations
|
||||
{
|
||||
public ImmutableArray<ParsedAttestation> Attestations { get; init; } = [];
|
||||
public ImmutableArray<ParsedAffirmation> Affirmations { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedAttestation
|
||||
{
|
||||
public ImmutableArray<string> Subjects { get; init; } = [];
|
||||
public string? Predicate { get; init; }
|
||||
public string? Evidence { get; init; }
|
||||
public ParsedSignature? Signature { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedAffirmation
|
||||
{
|
||||
public string? Statement { get; init; }
|
||||
public ImmutableArray<string> Signatories { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedDefinitions
|
||||
{
|
||||
public ImmutableArray<ParsedStandard> Standards { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ParsedStandard
|
||||
{
|
||||
public string? BomRef { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public string? Version { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public ParsedOrganization? Owner { get; init; }
|
||||
public ImmutableArray<string> Requirements { get; init; } = [];
|
||||
public ImmutableArray<ParsedExternalRef> ExternalReferences { get; init; } = [];
|
||||
public ParsedSignature? Signature { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ParsedSignature
|
||||
{
|
||||
public string? Algorithm { get; init; }
|
||||
public string? KeyId { get; init; }
|
||||
public string? PublicKey { get; init; }
|
||||
public ImmutableArray<string> CertificatePath { get; init; } = [];
|
||||
public string? Value { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// IParsedSbomParser.cs
|
||||
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
|
||||
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM extraction
|
||||
// Description: Interface for enriched SBOM parsing
|
||||
// -----------------------------------------------------------------------------
|
||||
using StellaOps.Concelier.SbomIntegration.Models;
|
||||
|
||||
namespace StellaOps.Concelier.SbomIntegration.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Service for parsing SBOM content into enriched ParsedSbom models.
|
||||
/// </summary>
|
||||
public interface IParsedSbomParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses SBOM content into a ParsedSbom model.
|
||||
/// </summary>
|
||||
/// <param name="content">SBOM content stream.</param>
|
||||
/// <param name="format">SBOM format.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Parsed SBOM with enriched metadata.</returns>
|
||||
Task<ParsedSbom> ParseAsync(
|
||||
Stream content,
|
||||
SbomFormat format,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
// Register parser
|
||||
services.TryAddSingleton<ISbomParser, SbomParser>();
|
||||
services.TryAddSingleton<IParsedSbomParser, ParsedSbomParser>();
|
||||
|
||||
// Register PURL index (requires Valkey connection)
|
||||
services.TryAddSingleton<IPurlCanonicalIndex, ValkeyPurlCanonicalIndex>();
|
||||
@@ -52,6 +53,7 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
// Register parser
|
||||
services.TryAddSingleton<ISbomParser, SbomParser>();
|
||||
services.TryAddSingleton<IParsedSbomParser, ParsedSbomParser>();
|
||||
|
||||
// Register PURL index (requires Valkey connection)
|
||||
services.TryAddSingleton<IPurlCanonicalIndex, ValkeyPurlCanonicalIndex>();
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
# Concelier SbomIntegration Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`,
|
||||
`docs/implplan/SPRINT_20260119_015_Concelier_sbom_full_extraction.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0237-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0237-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0237-A | TODO | Revalidated 2026-01-07 (open findings). |
|
||||
| TASK-015-001 | DOING | ParsedSbom model scaffolding. |
|
||||
| TASK-015-002 | DOING | ParsedService model scaffolding. |
|
||||
| TASK-015-003 | DOING | ParsedCryptoProperties model scaffolding. |
|
||||
| TASK-015-004 | DOING | ParsedModelCard model scaffolding. |
|
||||
| TASK-015-005 | DOING | CycloneDX formulation parsing + tests added; SPDX build parsing added. |
|
||||
| TASK-015-006 | DOING | ParsedVulnerability/VEX model scaffolding. |
|
||||
| TASK-015-007 | DOING | ParsedLicense model scaffolding. |
|
||||
| TASK-015-007a | DOING | CycloneDX license extraction expansion. |
|
||||
| TASK-015-007b | DOING | SPDX licensing profile extraction expansion. |
|
||||
| TASK-015-008 | DOING | CycloneDX extraction now covers formulation; tests updated. |
|
||||
| TASK-015-009 | DOING | ParsedSbomParser SPDX 3.0.1 extraction baseline + build profile. |
|
||||
| TASK-015-010 | DOING | ParsedSbom adapter + framework reference added; Artifact.Infrastructure build errors block tests. |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="TemporalCacheTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_001_TEST_time_skew_idempotency
|
||||
// Task: TSKW-010
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ConcelierConfigDiffTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
|
||||
// Task: CCUT-020
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="AstraConnectorTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ParsedSbomParserTests.cs
|
||||
// Sprint: SPRINT_20260119_015_Concelier_sbom_full_extraction
|
||||
// Task: TASK-015-008, TASK-015-009 - Parsed SBOM parsing tests
|
||||
// Description: Unit tests for enriched SBOM parsing
|
||||
// -----------------------------------------------------------------------------
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using StellaOps.Concelier.SbomIntegration.Models;
|
||||
using StellaOps.Concelier.SbomIntegration.Parsing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.SbomIntegration.Tests;
|
||||
|
||||
public sealed class ParsedSbomParserTests
|
||||
{
|
||||
private readonly ParsedSbomParser _parser;
|
||||
|
||||
public ParsedSbomParserTests()
|
||||
{
|
||||
var loggerMock = new Mock<ILogger<ParsedSbomParser>>();
|
||||
_parser = new ParsedSbomParser(loggerMock.Object);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ParseAsync_CycloneDx_ExtractsMetadataComponentsAndServices()
|
||||
{
|
||||
var content = """
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": "urn:uuid:1234",
|
||||
"metadata": {
|
||||
"timestamp": "2026-01-20T00:00:00Z",
|
||||
"component": {
|
||||
"bom-ref": "app",
|
||||
"name": "myapp",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tools": [
|
||||
{ "name": "stella-scanner" }
|
||||
],
|
||||
"authors": [
|
||||
{ "name": "dev@example.com" }
|
||||
],
|
||||
"supplier": { "name": "Acme" },
|
||||
"manufacturer": { "name": "AcmeManu" }
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "lib",
|
||||
"name": "lib",
|
||||
"version": "2.0.0",
|
||||
"purl": "pkg:npm/lib@2.0.0",
|
||||
"scope": "optional",
|
||||
"modified": true,
|
||||
"supplier": {
|
||||
"name": "LibSupplier",
|
||||
"url": "https://supplier.example.com",
|
||||
"contact": [
|
||||
{ "name": "Supplier Contact", "email": "contact@example.com" }
|
||||
]
|
||||
},
|
||||
"manufacturer": {
|
||||
"name": "LibManufacturer"
|
||||
},
|
||||
"evidence": {
|
||||
"identity": {
|
||||
"field": "purl",
|
||||
"confidence": 0.9,
|
||||
"value": "pkg:npm/lib@2.0.0"
|
||||
},
|
||||
"occurrences": [
|
||||
{
|
||||
"location": "src/lib.js",
|
||||
"line": 10,
|
||||
"offset": 4,
|
||||
"symbol": "libfn",
|
||||
"additionalContext": "ctx"
|
||||
}
|
||||
],
|
||||
"callstack": {
|
||||
"frames": [
|
||||
{
|
||||
"package": "pkg",
|
||||
"module": "mod",
|
||||
"function": "fn",
|
||||
"parameters": ["a", "b"],
|
||||
"line": 20,
|
||||
"column": 2,
|
||||
"fullFilename": "/src/lib.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"licenses": [
|
||||
{ "expression": "MIT" }
|
||||
],
|
||||
"copyright": [
|
||||
"Copyright 2026"
|
||||
]
|
||||
},
|
||||
"pedigree": {
|
||||
"ancestors": [
|
||||
{ "bom-ref": "ancestor", "version": "0.1", "description": "base" }
|
||||
],
|
||||
"variants": [
|
||||
{ "bom-ref": "variant", "version": "2.0" }
|
||||
],
|
||||
"commits": [
|
||||
{ "uid": "abc123", "message": "fix" }
|
||||
],
|
||||
"patches": [
|
||||
{ "type": "backport", "diff": { "text": "diff", "url": "https://example.com/diff" } }
|
||||
],
|
||||
"notes": ["note1", "note2"]
|
||||
},
|
||||
"cryptoProperties": {
|
||||
"assetType": "algorithm",
|
||||
"algorithmProperties": {
|
||||
"primitive": "hash",
|
||||
"parameterSetIdentifier": "ps1",
|
||||
"curve": "P-256",
|
||||
"executionEnvironment": "software",
|
||||
"certificationLevel": "fips140-2",
|
||||
"mode": "gcm",
|
||||
"padding": "pkcs7",
|
||||
"cryptoFunctions": ["digest"],
|
||||
"classicalSecurityLevel": 128,
|
||||
"nistQuantumSecurityLevel": 1,
|
||||
"keySize": 256
|
||||
},
|
||||
"certificateProperties": {
|
||||
"subjectName": "CN=Test",
|
||||
"issuerName": "CA",
|
||||
"notValidBefore": "2026-01-01T00:00:00Z",
|
||||
"notValidAfter": "2027-01-01T00:00:00Z",
|
||||
"signatureAlgorithmRef": "sha256",
|
||||
"subjectPublicKeyRef": "key",
|
||||
"certificateFormat": "x509",
|
||||
"certificateExtension": "ext"
|
||||
},
|
||||
"protocolProperties": {
|
||||
"type": "tls",
|
||||
"version": "1.3",
|
||||
"cipherSuites": ["TLS_AES_128_GCM_SHA256"],
|
||||
"ikev2TransformTypes": ["aes"],
|
||||
"cryptoRefArray": ["ref1"]
|
||||
},
|
||||
"relatedCryptoMaterialProperties": {
|
||||
"type": "key",
|
||||
"id": "key-1",
|
||||
"algorithmRef": "alg-1",
|
||||
"securedBy": ["sec1"],
|
||||
"relatedCryptographicAssets": [
|
||||
{ "type": "certificate", "ref": "cert-1" }
|
||||
]
|
||||
},
|
||||
"oid": "1.2.3.4"
|
||||
},
|
||||
"modelCard": {
|
||||
"bom-ref": "model-1",
|
||||
"modelParameters": {
|
||||
"task": "classification",
|
||||
"architectureFamily": "cnn",
|
||||
"modelArchitecture": "resnet",
|
||||
"approach": { "type": "supervised" },
|
||||
"datasets": [
|
||||
{
|
||||
"name": "dataset1",
|
||||
"version": "1.0",
|
||||
"url": "https://example.com/ds",
|
||||
"hashes": [
|
||||
{ "alg": "SHA-256", "content": "abcd" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{ "format": "image", "description": "jpg" }
|
||||
],
|
||||
"outputs": [
|
||||
{ "format": "label", "description": "class" }
|
||||
]
|
||||
},
|
||||
"quantitativeAnalysis": {
|
||||
"performanceMetrics": [
|
||||
{
|
||||
"type": "accuracy",
|
||||
"value": "0.9",
|
||||
"slice": "overall",
|
||||
"confidenceInterval": { "lowerBound": "0.88", "upperBound": "0.92" }
|
||||
}
|
||||
],
|
||||
"graphics": {
|
||||
"collection": [
|
||||
{ "name": "roc", "image": "data:image/png;base64,aa", "description": "ROC" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"considerations": {
|
||||
"users": ["devs"],
|
||||
"useCases": ["testing"],
|
||||
"technicalLimitations": ["small dataset"],
|
||||
"ethicalConsiderations": [
|
||||
{ "name": "bias", "mitigationStrategy": "review" }
|
||||
],
|
||||
"fairnessAssessments": [
|
||||
{ "groupAtRisk": "group1", "benefits": "benefit", "harms": "harm", "mitigationStrategy": "mit" }
|
||||
],
|
||||
"environmentalConsiderations": {
|
||||
"energyConsumptions": [
|
||||
{
|
||||
"activity": "training",
|
||||
"activityEnergyCost": "10kWh",
|
||||
"co2CostEquivalent": "5kg",
|
||||
"co2CostOffset": "1kg",
|
||||
"properties": [
|
||||
{ "name": "region", "value": "us" }
|
||||
],
|
||||
"energyProviders": [
|
||||
{
|
||||
"bom-ref": "prov",
|
||||
"description": "provider",
|
||||
"organization": { "name": "EnergyCo" },
|
||||
"energySource": "solar",
|
||||
"energyProvided": "5kWh",
|
||||
"externalReferences": [
|
||||
{ "type": "website", "url": "https://energy.example.com" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "note", "value": "env" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT",
|
||||
"name": "MIT License",
|
||||
"url": "https://example.com/license",
|
||||
"text": {
|
||||
"content": "TUlU",
|
||||
"encoding": "base64"
|
||||
},
|
||||
"licensing": {
|
||||
"licensor": { "name": "Acme" },
|
||||
"licensee": "Consumer",
|
||||
"purchaseOrder": "PO-123"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expression": "MIT AND (Apache-2.0 OR BSD-3-Clause)"
|
||||
}
|
||||
],
|
||||
"externalReferences": [
|
||||
{ "type": "website", "url": "https://example.com/lib", "comment": "home" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"ref": "app",
|
||||
"dependsOn": ["lib"]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"bom-ref": "svc",
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"authenticated": true,
|
||||
"x-trust-boundary": true,
|
||||
"endpoints": ["https://api.example.com"],
|
||||
"licenses": [
|
||||
{ "expression": "Apache-2.0" }
|
||||
],
|
||||
"externalReferences": [
|
||||
{ "type": "documentation", "url": "https://example.com/api-docs" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"formulation": [
|
||||
{
|
||||
"bom-ref": "form-1",
|
||||
"components": [
|
||||
"lib",
|
||||
{
|
||||
"ref": "app",
|
||||
"properties": [
|
||||
{ "name": "stage", "value": "build" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"workflows": [
|
||||
{
|
||||
"name": "build",
|
||||
"description": "build pipeline",
|
||||
"inputs": ["src"],
|
||||
"outputs": ["artifact"],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "compile",
|
||||
"description": "compile sources",
|
||||
"inputs": ["src"],
|
||||
"outputs": ["bin"],
|
||||
"parameters": [
|
||||
{ "name": "opt", "value": "O2" }
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "runner", "value": "msbuild" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "workflow", "value": "ci" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "package",
|
||||
"description": "package app",
|
||||
"inputs": ["bin"],
|
||||
"outputs": ["artifact"],
|
||||
"parameters": [
|
||||
{ "name": "format", "value": "zip" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "formulation", "value": "v1" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var result = await _parser.ParseAsync(stream, SbomFormat.CycloneDX);
|
||||
|
||||
result.Format.Should().Be("cyclonedx");
|
||||
result.SpecVersion.Should().Be("1.7");
|
||||
result.SerialNumber.Should().Be("urn:uuid:1234");
|
||||
result.Metadata.Name.Should().Be("myapp");
|
||||
result.Metadata.Version.Should().Be("1.0.0");
|
||||
result.Metadata.Supplier.Should().Be("Acme");
|
||||
result.Metadata.Manufacturer.Should().Be("AcmeManu");
|
||||
result.Components.Should().Contain(c => c.BomRef == "app");
|
||||
result.Components.Should().Contain(c => c.Purl == "pkg:npm/lib@2.0.0");
|
||||
var lib = result.Components.Single(c => c.BomRef == "lib");
|
||||
lib.ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "website" && r.Url == "https://example.com/lib");
|
||||
lib.Scope.Should().Be(ComponentScope.Optional);
|
||||
lib.Modified.Should().BeTrue();
|
||||
lib.Supplier.Should().NotBeNull();
|
||||
lib.Supplier!.Name.Should().Be("LibSupplier");
|
||||
lib.Supplier!.Url.Should().Be("https://supplier.example.com");
|
||||
lib.Manufacturer.Should().NotBeNull();
|
||||
lib.Manufacturer!.Name.Should().Be("LibManufacturer");
|
||||
lib.Evidence.Should().NotBeNull();
|
||||
lib.Evidence!.Identity.Should().NotBeNull();
|
||||
lib.Evidence!.Identity!.Field.Should().Be("purl");
|
||||
lib.Evidence!.Identity!.Confidence.Should().Be(0.9);
|
||||
lib.Evidence!.Occurrences.Should().ContainSingle(o => o.Location == "src/lib.js");
|
||||
lib.Evidence!.Callstack.Should().NotBeNull();
|
||||
lib.Evidence!.Callstack!.Frames.Should().ContainSingle(f => f.Function == "fn");
|
||||
lib.Evidence!.Licenses.Should().ContainSingle(l => l.Expression != null);
|
||||
lib.Evidence!.Copyrights.Should().ContainSingle(c => c == "Copyright 2026");
|
||||
lib.Pedigree.Should().NotBeNull();
|
||||
lib.Pedigree!.Ancestors.Should().ContainSingle(a => a.BomRef == "ancestor");
|
||||
lib.Pedigree!.Variants.Should().ContainSingle(v => v.BomRef == "variant");
|
||||
lib.Pedigree!.Commits.Should().ContainSingle(c => c.BomRef == "abc123");
|
||||
lib.Pedigree!.Patches.Should().ContainSingle(p => p.Type == "backport");
|
||||
lib.Pedigree!.Notes.Should().Contain(n => n == "note1");
|
||||
lib.CryptoProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.AssetType.Should().Be(CryptoAssetType.Algorithm);
|
||||
lib.CryptoProperties!.AlgorithmProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.AlgorithmProperties!.Primitive.Should().Be(CryptoPrimitive.Hash);
|
||||
lib.CryptoProperties!.CertificateProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.CertificateProperties!.SubjectName.Should().Be("CN=Test");
|
||||
lib.CryptoProperties!.ProtocolProperties.Should().NotBeNull();
|
||||
lib.CryptoProperties!.ProtocolProperties!.Type.Should().Be("tls");
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial.Should().NotBeNull();
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial!.Reference.Should().Be("key-1");
|
||||
lib.CryptoProperties!.RelatedCryptoMaterial!.MaterialRefs.Should().Contain("cert-1");
|
||||
lib.ModelCard.Should().NotBeNull();
|
||||
lib.ModelCard!.BomRef.Should().Be("model-1");
|
||||
lib.ModelCard!.ModelParameters.Should().NotBeNull();
|
||||
lib.ModelCard!.ModelParameters!.Task.Should().Be("classification");
|
||||
lib.ModelCard!.ModelParameters!.Datasets.Should().ContainSingle(d => d.Name == "dataset1");
|
||||
lib.ModelCard!.QuantitativeAnalysis.Should().NotBeNull();
|
||||
lib.ModelCard!.QuantitativeAnalysis!.PerformanceMetrics.Should().ContainSingle(m => m.Type == "accuracy");
|
||||
lib.ModelCard!.Considerations.Should().NotBeNull();
|
||||
lib.ModelCard!.Considerations!.Users.Should().ContainSingle(u => u == "devs");
|
||||
lib.Licenses.Should().HaveCount(2);
|
||||
lib.Licenses[0].SpdxId.Should().Be("MIT");
|
||||
lib.Licenses[0].Name.Should().Be("MIT License");
|
||||
lib.Licenses[0].Url.Should().Be("https://example.com/license");
|
||||
lib.Licenses[0].Text.Should().Be("MIT");
|
||||
lib.Licenses[0].Licensing.Should().NotBeNull();
|
||||
lib.Licenses[0].Licensing!.Licensor.Should().Be("Acme");
|
||||
lib.Licenses[0].Licensing!.Licensee.Should().Be("Consumer");
|
||||
lib.Licenses[0].Licensing!.PurchaseOrder.Should().Be("PO-123");
|
||||
lib.Licenses[1].Expression.Should().BeOfType<ConjunctiveSet>();
|
||||
var andExpr = (ConjunctiveSet)lib.Licenses[1].Expression!;
|
||||
andExpr.Members.Should().HaveCount(2);
|
||||
andExpr.Members[0].Should().BeOfType<SimpleLicense>()
|
||||
.Which.Id.Should().Be("MIT");
|
||||
andExpr.Members[1].Should().BeOfType<DisjunctiveSet>();
|
||||
var orExpr = (DisjunctiveSet)andExpr.Members[1];
|
||||
orExpr.Members.OfType<SimpleLicense>()
|
||||
.Should()
|
||||
.ContainSingle(license => license.Id == "Apache-2.0");
|
||||
result.Dependencies.Should().ContainSingle(d => d.SourceRef == "app");
|
||||
result.Services.Should().ContainSingle(s => s.Name == "api");
|
||||
result.Services[0].Authenticated.Should().BeTrue();
|
||||
result.Services[0].CrossesTrustBoundary.Should().BeTrue();
|
||||
result.Services[0].Licenses.Should().ContainSingle();
|
||||
result.Services[0].ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "documentation" && r.Url == "https://example.com/api-docs");
|
||||
result.Formulation.Should().NotBeNull();
|
||||
result.Formulation!.BomRef.Should().Be("form-1");
|
||||
result.Formulation.Components.Should().HaveCount(2);
|
||||
result.Formulation.Components.Should().ContainSingle(c => c.BomRef == "lib");
|
||||
result.Formulation.Components.Should().ContainSingle(c =>
|
||||
c.BomRef == "app" && c.Properties.ContainsKey("stage"));
|
||||
result.Formulation.Workflows.Should().ContainSingle(w => w.Name == "build");
|
||||
var workflow = result.Formulation.Workflows.Single(w => w.Name == "build");
|
||||
workflow.InputRefs.Should().Contain("src");
|
||||
workflow.OutputRefs.Should().Contain("artifact");
|
||||
workflow.Tasks.Should().ContainSingle(t => t.Name == "compile");
|
||||
workflow.Tasks[0].Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
|
||||
workflow.Tasks[0].Properties.Should().ContainKey("runner").WhoseValue.Should().Be("msbuild");
|
||||
result.Formulation.Tasks.Should().ContainSingle(t => t.Name == "package");
|
||||
result.Formulation.Tasks[0].Parameters.Should().ContainKey("format").WhoseValue.Should().Be("zip");
|
||||
result.Formulation.Properties.Should().ContainKey("formulation").WhoseValue.Should().Be("v1");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ParseAsync_Spdx3_ExtractsDocumentAndPackageMetadata()
|
||||
{
|
||||
var content = """
|
||||
{
|
||||
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "SpdxDocument",
|
||||
"spdxId": "urn:doc",
|
||||
"name": "sbom-doc",
|
||||
"creationInfo": {
|
||||
"specVersion": "3.0.1",
|
||||
"created": "2026-01-20T00:00:00Z",
|
||||
"createdBy": ["org:Acme"],
|
||||
"createdUsing": ["tool:stella"],
|
||||
"profile": ["https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType/core"]
|
||||
},
|
||||
"rootElement": ["spdx:pkg:root"],
|
||||
"namespaceMap": [
|
||||
{ "prefix": "ex", "namespace": "https://example.com" }
|
||||
],
|
||||
"import": [
|
||||
{ "externalSpdxId": "urn:ext" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "build_Build",
|
||||
"spdxId": "spdx:build:1",
|
||||
"buildId": "build-123",
|
||||
"buildType": "release",
|
||||
"buildStartTime": "2026-01-20T01:00:00Z",
|
||||
"buildEndTime": "2026-01-20T01:30:00Z",
|
||||
"configSourceEntrypoint": "build.yaml",
|
||||
"configSourceDigest": "sha256:abcd",
|
||||
"configSourceUri": "https://example.com/build.yaml",
|
||||
"environment": [
|
||||
{ "name": "OS", "value": "linux" }
|
||||
],
|
||||
"parameters": {
|
||||
"opt": "O2",
|
||||
"threads": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "software_Package",
|
||||
"spdxId": "spdx:pkg:root",
|
||||
"name": "root",
|
||||
"software_packageVersion": "1.0.0",
|
||||
"packageUrl": "pkg:npm/root@1.0.0",
|
||||
"simplelicensing_licenseExpression": "Apache-2.0 WITH LLVM-exception",
|
||||
"verifiedUsing": [
|
||||
{ "algorithm": "SHA256", "hashValue": "abc" }
|
||||
],
|
||||
"externalRef": [
|
||||
{ "externalRefType": "purl", "locator": "pkg:npm/root@1.0.0", "comment": "source" }
|
||||
],
|
||||
"externalIdentifier": [
|
||||
{ "externalIdentifierType": "cpe23", "identifier": "cpe:2.3:a:root" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var result = await _parser.ParseAsync(stream, SbomFormat.SPDX);
|
||||
|
||||
result.Format.Should().Be("spdx");
|
||||
result.SpecVersion.Should().Be("3.0.1");
|
||||
result.SerialNumber.Should().Be("urn:doc");
|
||||
result.Metadata.Name.Should().Be("sbom-doc");
|
||||
result.Metadata.RootComponentRef.Should().Be("spdx:pkg:root");
|
||||
result.Metadata.NamespaceMap.Should().ContainSingle(map => map.Prefix == "ex");
|
||||
result.BuildInfo.Should().NotBeNull();
|
||||
result.BuildInfo!.BuildId.Should().Be("build-123");
|
||||
result.BuildInfo.BuildType.Should().Be("release");
|
||||
result.BuildInfo.BuildStartTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:00:00Z"));
|
||||
result.BuildInfo.BuildEndTime.Should().Be(DateTimeOffset.Parse("2026-01-20T01:30:00Z"));
|
||||
result.BuildInfo.ConfigSourceEntrypoint.Should().Be("build.yaml");
|
||||
result.BuildInfo.ConfigSourceDigest.Should().Be("sha256:abcd");
|
||||
result.BuildInfo.ConfigSourceUri.Should().Be("https://example.com/build.yaml");
|
||||
result.BuildInfo.Environment.Should().ContainKey("OS").WhoseValue.Should().Be("linux");
|
||||
result.BuildInfo.Parameters.Should().ContainKey("opt").WhoseValue.Should().Be("O2");
|
||||
result.BuildInfo.Parameters.Should().ContainKey("threads").WhoseValue.Should().Be("8");
|
||||
var root = result.Components.Single(c => c.Purl == "pkg:npm/root@1.0.0");
|
||||
root.Cpe.Should().Be("cpe:2.3:a:root");
|
||||
root.Hashes.Should().ContainSingle(h => h.Algorithm == "SHA256" && h.Value == "abc");
|
||||
root.ExternalReferences.Should().ContainSingle(r =>
|
||||
r.Type == "purl" && r.Url == "pkg:npm/root@1.0.0");
|
||||
root.Licenses.Should().ContainSingle();
|
||||
root.Licenses[0].Expression.Should().BeOfType<WithException>();
|
||||
var withExpr = (WithException)root.Licenses[0].Expression!;
|
||||
withExpr.Exception.Should().Be("LLVM-exception");
|
||||
withExpr.License.Should().BeOfType<SimpleLicense>()
|
||||
.Which.Id.Should().Be("Apache-2.0");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// <copyright file="ConcelierSchemaEvolutionTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
|
||||
// Task: CCUT-010
|
||||
|
||||
Reference in New Issue
Block a user