using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Time.Testing; using StellaOps.Concelier.Merge.Options; using StellaOps.Concelier.Merge.Services; using StellaOps.Concelier.Models; namespace StellaOps.Concelier.Merge.Tests; public sealed class AdvisoryPrecedenceMergerTests { [Fact] public void Merge_PrefersVendorPrecedenceOverNvd() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero)); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); using var metrics = new MetricCollector("StellaOps.Concelier.Merge"); var (redHat, nvd) = CreateVendorAndRegistryAdvisories(); var expectedMergeTimestamp = timeProvider.GetUtcNow(); var merged = merger.Merge(new[] { nvd, redHat }).Advisory; Assert.Equal("CVE-2025-1000", merged.AdvisoryKey); Assert.Equal("Red Hat Security Advisory", merged.Title); Assert.Equal("Vendor-confirmed impact on RHEL 9.", merged.Summary); Assert.Equal("high", merged.Severity); Assert.Equal(redHat.Published, merged.Published); Assert.Equal(redHat.Modified, merged.Modified); Assert.Contains("RHSA-2025:0001", merged.Aliases); Assert.Contains("CVE-2025-1000", merged.Aliases); var package = Assert.Single(merged.AffectedPackages); Assert.Equal("cpe:2.3:o:redhat:enterprise_linux:9:*:*:*:*:*:*:*", package.Identifier); Assert.Empty(package.VersionRanges); // NVD range suppressed by vendor precedence Assert.Contains(package.Statuses, status => status.Status == "known_affected"); Assert.Contains(package.Provenance, provenance => provenance.Source == "redhat"); Assert.Contains(package.Provenance, provenance => provenance.Source == "nvd"); Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "redhat"); Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "nvd"); var mergeProvenance = merged.Provenance.Single(p => p.Source == "merge"); Assert.Equal("precedence", mergeProvenance.Kind); Assert.Equal(expectedMergeTimestamp, mergeProvenance.RecordedAt); Assert.Contains("redhat", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); Assert.Contains("nvd", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); var rangeMeasurement = Assert.Single(metrics.Measurements, measurement => measurement.Name == "concelier.merge.range_overrides"); Assert.Equal(1, rangeMeasurement.Value); Assert.Contains(rangeMeasurement.Tags, tag => string.Equals(tag.Key, "suppressed_source", StringComparison.Ordinal) && tag.Value?.ToString()?.Contains("nvd", StringComparison.OrdinalIgnoreCase) == true); var severityConflict = Assert.Single(metrics.Measurements, measurement => measurement.Name == "concelier.merge.conflicts"); Assert.Equal(1, severityConflict.Value); Assert.Contains(severityConflict.Tags, tag => string.Equals(tag.Key, "type", StringComparison.Ordinal) && string.Equals(tag.Value?.ToString(), "severity", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Merge_KevOnlyTogglesExploitKnown() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 2, 1, 0, 0, 0, TimeSpan.Zero)); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); var nvdProvenance = new AdvisoryProvenance("nvd", "document", "https://nvd", timeProvider.GetUtcNow()); var baseAdvisory = new Advisory( "CVE-2025-2000", "CVE-2025-2000", "Base registry summary", "en", new DateTimeOffset(2025, 1, 5, 0, 0, 0, TimeSpan.Zero), new DateTimeOffset(2025, 1, 6, 0, 0, 0, TimeSpan.Zero), "medium", exploitKnown: false, aliases: new[] { "CVE-2025-2000" }, credits: Array.Empty(), references: Array.Empty(), affectedPackages: new[] { new AffectedPackage( AffectedPackageTypes.Cpe, "cpe:2.3:a:example:product:2.0:*:*:*:*:*:*:*", null, new[] { new AffectedVersionRange( "semver", "2.0.0", "2.0.5", null, "<2.0.5", new AdvisoryProvenance("nvd", "cpe_match", "product", timeProvider.GetUtcNow())) }, Array.Empty(), new[] { nvdProvenance }) }, cvssMetrics: Array.Empty(), provenance: new[] { nvdProvenance }); var kevProvenance = new AdvisoryProvenance("kev", "catalog", "CVE-2025-2000", timeProvider.GetUtcNow()); var kevAdvisory = new Advisory( "CVE-2025-2000", "Known Exploited Vulnerability", summary: null, language: null, published: null, modified: null, severity: null, exploitKnown: true, aliases: new[] { "KEV-CVE-2025-2000" }, credits: Array.Empty(), references: Array.Empty(), affectedPackages: Array.Empty(), cvssMetrics: Array.Empty(), provenance: new[] { kevProvenance }); var merged = merger.Merge(new[] { baseAdvisory, kevAdvisory }).Advisory; Assert.True(merged.ExploitKnown); Assert.Equal("medium", merged.Severity); // KEV must not override severity Assert.Equal("Base registry summary", merged.Summary); Assert.Contains("CVE-2025-2000", merged.Aliases); Assert.Contains("KEV-CVE-2025-2000", merged.Aliases); Assert.Contains(merged.Provenance, provenance => provenance.Source == "kev"); Assert.Contains(merged.Provenance, provenance => provenance.Source == "merge"); } [Fact] public void Merge_UnionsCreditsFromSources() { var timeProvider = new FakeTimeProvider(); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); var ghsaCredits = new[] { new AdvisoryCredit( displayName: "maintainer-team", role: "remediation_developer", contacts: new[] { "https://github.com/maintainer-team" }, provenance: new AdvisoryProvenance( "ghsa", "credit", "mantainer-team", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Credits })), new AdvisoryCredit( displayName: "security-reporter", role: "reporter", contacts: new[] { "https://github.com/security-reporter" }, provenance: new AdvisoryProvenance( "ghsa", "credit", "security-reporter", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Credits })), }; var ghsa = new Advisory( "CVE-2025-9000", "GHSA advisory", "Reported in GHSA", "en", timeProvider.GetUtcNow(), timeProvider.GetUtcNow(), "high", exploitKnown: false, aliases: new[] { "GHSA-aaaa-bbbb-cccc", "CVE-2025-9000" }, credits: ghsaCredits, references: Array.Empty(), affectedPackages: Array.Empty(), cvssMetrics: Array.Empty(), provenance: new[] { new AdvisoryProvenance("ghsa", "document", "https://github.com/advisories/GHSA-aaaa-bbbb-cccc", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Advisory }) }); var osvCredits = new[] { new AdvisoryCredit( displayName: "osv-researcher", role: "reporter", contacts: new[] { "mailto:osv-researcher@example.com" }, provenance: new AdvisoryProvenance( "osv", "credit", "osv-researcher", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Credits })), new AdvisoryCredit( displayName: "maintainer-team", role: "remediation_developer", contacts: new[] { "https://github.com/maintainer-team" }, provenance: new AdvisoryProvenance( "osv", "credit", "maintainer-team", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Credits })), }; var osv = new Advisory( "CVE-2025-9000", "OSV advisory", "Reported in OSV.dev", "en", timeProvider.GetUtcNow().AddDays(-1), timeProvider.GetUtcNow().AddHours(-1), "medium", exploitKnown: false, aliases: new[] { "CVE-2025-9000" }, credits: osvCredits, references: Array.Empty(), affectedPackages: Array.Empty(), cvssMetrics: Array.Empty(), provenance: new[] { new AdvisoryProvenance("osv", "document", "https://osv.dev/vulnerability/CVE-2025-9000", timeProvider.GetUtcNow(), new[] { ProvenanceFieldMasks.Advisory }) }); var merged = merger.Merge(new[] { ghsa, osv }).Advisory; Assert.Equal("CVE-2025-9000", merged.AdvisoryKey); Assert.Contains(merged.Credits, credit => string.Equals(credit.DisplayName, "maintainer-team", StringComparison.OrdinalIgnoreCase) && string.Equals(credit.Role, "remediation_developer", StringComparison.OrdinalIgnoreCase)); Assert.Contains(merged.Credits, credit => string.Equals(credit.DisplayName, "osv-researcher", StringComparison.OrdinalIgnoreCase) && string.Equals(credit.Role, "reporter", StringComparison.OrdinalIgnoreCase)); Assert.Contains(merged.Credits, credit => string.Equals(credit.DisplayName, "security-reporter", StringComparison.OrdinalIgnoreCase) && string.Equals(credit.Role, "reporter", StringComparison.OrdinalIgnoreCase)); Assert.Contains(merged.Credits, credit => credit.Provenance.Source == "ghsa"); Assert.Contains(merged.Credits, credit => credit.Provenance.Source == "osv"); } [Fact] public void Merge_AcscActsAsEnrichmentSource() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero)); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); var vendorDocumentProvenance = new AdvisoryProvenance( source: "vndr-cisco", kind: "document", value: "https://vendor.example/advisories/router-critical", recordedAt: timeProvider.GetUtcNow(), fieldMask: new[] { ProvenanceFieldMasks.Advisory }); var vendorReference = new AdvisoryReference( "https://vendor.example/advisories/router-critical", kind: "advisory", sourceTag: "vendor", summary: "Vendor advisory", provenance: new AdvisoryProvenance("vndr-cisco", "reference", "https://vendor.example/advisories/router-critical", timeProvider.GetUtcNow())); var vendorPackage = new AffectedPackage( AffectedPackageTypes.Vendor, "ExampleCo Router X", platform: null, versionRanges: Array.Empty(), statuses: Array.Empty(), normalizedVersions: Array.Empty(), provenance: new[] { vendorDocumentProvenance }); var vendor = new Advisory( advisoryKey: "acsc-2025-010", title: "Vendor Critical Router Advisory", summary: "Vendor-confirmed exploit.", language: "en", published: new DateTimeOffset(2025, 10, 11, 23, 0, 0, TimeSpan.Zero), modified: new DateTimeOffset(2025, 10, 11, 23, 30, 0, TimeSpan.Zero), severity: "critical", exploitKnown: false, aliases: new[] { "VENDOR-2025-010" }, references: new[] { vendorReference }, affectedPackages: new[] { vendorPackage }, cvssMetrics: Array.Empty(), provenance: new[] { vendorDocumentProvenance }); var acscDocumentProvenance = new AdvisoryProvenance( source: "acsc", kind: "document", value: "https://origin.example/feeds/alerts/rss", recordedAt: timeProvider.GetUtcNow(), fieldMask: new[] { ProvenanceFieldMasks.Advisory }); var acscReference = new AdvisoryReference( "https://origin.example/advisories/router-critical", kind: "advisory", sourceTag: "acsc", summary: "ACSC alert", provenance: new AdvisoryProvenance("acsc", "reference", "https://origin.example/advisories/router-critical", timeProvider.GetUtcNow())); var acscPackage = new AffectedPackage( AffectedPackageTypes.Vendor, "ExampleCo Router X", platform: null, versionRanges: Array.Empty(), statuses: Array.Empty(), normalizedVersions: Array.Empty(), provenance: new[] { acscDocumentProvenance }); var acsc = new Advisory( advisoryKey: "acsc-2025-010", title: "ACSC Router Alert", summary: "ACSC recommends installing vendor update.", language: "en", published: new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero), modified: null, severity: "medium", exploitKnown: false, aliases: new[] { "ACSC-2025-010" }, references: new[] { acscReference }, affectedPackages: new[] { acscPackage }, cvssMetrics: Array.Empty(), provenance: new[] { acscDocumentProvenance }); var merged = merger.Merge(new[] { acsc, vendor }).Advisory; Assert.Equal("critical", merged.Severity); // ACSC must not override vendor severity Assert.Equal("Vendor-confirmed exploit.", merged.Summary); Assert.Contains("ACSC-2025-010", merged.Aliases); Assert.Contains("VENDOR-2025-010", merged.Aliases); Assert.Contains(merged.References, reference => reference.SourceTag == "vendor" && reference.Url == vendorReference.Url); Assert.Contains(merged.References, reference => reference.SourceTag == "acsc" && reference.Url == acscReference.Url); var enrichedPackage = Assert.Single(merged.AffectedPackages, package => package.Identifier == "ExampleCo Router X"); Assert.Contains(enrichedPackage.Provenance, provenance => provenance.Source == "vndr-cisco"); Assert.Contains(enrichedPackage.Provenance, provenance => provenance.Source == "acsc"); Assert.Contains(merged.Provenance, provenance => provenance.Source == "acsc"); Assert.Contains(merged.Provenance, provenance => provenance.Source == "vndr-cisco"); Assert.Contains(merged.Provenance, provenance => provenance.Source == "merge" && (provenance.Value?.Contains("acsc", StringComparison.OrdinalIgnoreCase) ?? false)); } [Fact] public void Merge_RecordsNormalizedRuleMetrics() { var now = new DateTimeOffset(2025, 3, 1, 0, 0, 0, TimeSpan.Zero); var timeProvider = new FakeTimeProvider(now); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); using var metrics = new MetricCollector("StellaOps.Concelier.Merge"); var normalizedRule = new NormalizedVersionRule( NormalizedVersionSchemes.SemVer, NormalizedVersionRuleTypes.Range, min: "1.0.0", minInclusive: true, max: "2.0.0", maxInclusive: false, notes: "ghsa:GHSA-xxxx-yyyy"); var ghsaProvenance = new AdvisoryProvenance("ghsa", "package", "pkg:npm/example", now); var ghsaPackage = new AffectedPackage( AffectedPackageTypes.SemVer, "pkg:npm/example", platform: null, versionRanges: new[] { new AffectedVersionRange( NormalizedVersionSchemes.SemVer, "1.0.0", "2.0.0", null, ">= 1.0.0 < 2.0.0", ghsaProvenance) }, statuses: Array.Empty(), provenance: new[] { ghsaProvenance, }, normalizedVersions: new[] { normalizedRule }); var nvdPackage = new AffectedPackage( AffectedPackageTypes.SemVer, "pkg:npm/example", platform: null, versionRanges: new[] { new AffectedVersionRange( NormalizedVersionSchemes.SemVer, "1.0.0", "2.0.0", null, ">= 1.0.0 < 2.0.0", new AdvisoryProvenance("nvd", "cpe_match", "pkg:npm/example", now)) }, statuses: Array.Empty(), provenance: new[] { new AdvisoryProvenance("nvd", "document", "https://nvd.nist.gov/vuln/detail/CVE-2025-7000", now), }, normalizedVersions: Array.Empty()); var nvdExclusivePackage = new AffectedPackage( AffectedPackageTypes.SemVer, "pkg:npm/another", platform: null, versionRanges: new[] { new AffectedVersionRange( NormalizedVersionSchemes.SemVer, "3.0.0", null, null, ">= 3.0.0", new AdvisoryProvenance("nvd", "cpe_match", "pkg:npm/another", now)) }, statuses: Array.Empty(), provenance: new[] { new AdvisoryProvenance("nvd", "document", "https://nvd.nist.gov/vuln/detail/CVE-2025-7000", now), }, normalizedVersions: Array.Empty()); var ghsaAdvisory = new Advisory( "CVE-2025-7000", "GHSA advisory", "GHSA summary", "en", now, now, "high", exploitKnown: false, aliases: new[] { "CVE-2025-7000", "GHSA-xxxx-yyyy" }, credits: Array.Empty(), references: Array.Empty(), affectedPackages: new[] { ghsaPackage }, cvssMetrics: Array.Empty(), provenance: new[] { new AdvisoryProvenance("ghsa", "document", "https://github.com/advisories/GHSA-xxxx-yyyy", now), }); var nvdAdvisory = new Advisory( "CVE-2025-7000", "NVD entry", "NVD summary", "en", now, now, "high", exploitKnown: false, aliases: new[] { "CVE-2025-7000" }, credits: Array.Empty(), references: Array.Empty(), affectedPackages: new[] { nvdPackage, nvdExclusivePackage }, cvssMetrics: Array.Empty(), provenance: new[] { new AdvisoryProvenance("nvd", "document", "https://nvd.nist.gov/vuln/detail/CVE-2025-7000", now), }); var merged = merger.Merge(new[] { nvdAdvisory, ghsaAdvisory }).Advisory; Assert.Equal(2, merged.AffectedPackages.Length); var normalizedPackage = Assert.Single(merged.AffectedPackages, pkg => pkg.Identifier == "pkg:npm/example"); Assert.Single(normalizedPackage.NormalizedVersions); var missingPackage = Assert.Single(merged.AffectedPackages, pkg => pkg.Identifier == "pkg:npm/another"); Assert.Empty(missingPackage.NormalizedVersions); Assert.NotEmpty(missingPackage.VersionRanges); var normalizedMeasurements = metrics.Measurements.Where(m => m.Name == "concelier.merge.normalized_rules").ToList(); Assert.Contains(normalizedMeasurements, measurement => measurement.Value == 1 && measurement.Tags.Any(tag => string.Equals(tag.Key, "scheme", StringComparison.Ordinal) && string.Equals(tag.Value?.ToString(), "semver", StringComparison.Ordinal)) && measurement.Tags.Any(tag => string.Equals(tag.Key, "package_type", StringComparison.Ordinal) && string.Equals(tag.Value?.ToString(), "semver", StringComparison.Ordinal))); var missingMeasurements = metrics.Measurements.Where(m => m.Name == "concelier.merge.normalized_rules_missing").ToList(); var missingMeasurement = Assert.Single(missingMeasurements); Assert.Equal(1, missingMeasurement.Value); Assert.Contains(missingMeasurement.Tags, tag => string.Equals(tag.Key, "package_type", StringComparison.Ordinal) && string.Equals(tag.Value?.ToString(), "semver", StringComparison.Ordinal)); } [Fact] public void Merge_RespectsConfiguredPrecedenceOverrides() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 3, 1, 0, 0, 0, TimeSpan.Zero)); var options = new AdvisoryPrecedenceOptions { Ranks = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["nvd"] = 0, ["redhat"] = 5, } }; var logger = new TestLogger(); using var metrics = new MetricCollector("StellaOps.Concelier.Merge"); var merger = new AdvisoryPrecedenceMerger( new AffectedPackagePrecedenceResolver(), options, timeProvider, logger); var (redHat, nvd) = CreateVendorAndRegistryAdvisories(); var merged = merger.Merge(new[] { redHat, nvd }).Advisory; Assert.Equal("CVE-2025-1000", merged.AdvisoryKey); Assert.Equal("CVE-2025-1000", merged.Title); // NVD preferred Assert.Equal("NVD summary", merged.Summary); Assert.Equal("medium", merged.Severity); var package = Assert.Single(merged.AffectedPackages); Assert.NotEmpty(package.VersionRanges); // Vendor range no longer overrides Assert.Contains(package.Provenance, provenance => provenance.Source == "nvd"); Assert.Contains(package.Provenance, provenance => provenance.Source == "redhat"); var overrideMeasurement = Assert.Single(metrics.Measurements, m => m.Name == "concelier.merge.overrides"); Assert.Equal(1, overrideMeasurement.Value); Assert.Contains(overrideMeasurement.Tags, tag => tag.Key == "primary_source" && string.Equals(tag.Value?.ToString(), "nvd", StringComparison.OrdinalIgnoreCase)); Assert.Contains(overrideMeasurement.Tags, tag => tag.Key == "suppressed_source" && tag.Value?.ToString()?.Contains("redhat", StringComparison.OrdinalIgnoreCase) == true); Assert.DoesNotContain(metrics.Measurements, measurement => measurement.Name == "concelier.merge.range_overrides"); var conflictMeasurement = Assert.Single(metrics.Measurements, measurement => measurement.Name == "concelier.merge.conflicts"); Assert.Equal(1, conflictMeasurement.Value); Assert.Contains(conflictMeasurement.Tags, tag => tag.Key == "type" && string.Equals(tag.Value?.ToString(), "severity", StringComparison.OrdinalIgnoreCase)); Assert.Contains(conflictMeasurement.Tags, tag => tag.Key == "reason" && string.Equals(tag.Value?.ToString(), "mismatch", StringComparison.OrdinalIgnoreCase)); var logEntry = Assert.Single(logger.Entries, entry => entry.EventId.Name == "AdvisoryOverride"); Assert.Equal(LogLevel.Information, logEntry.Level); Assert.NotNull(logEntry.StructuredState); Assert.Contains(logEntry.StructuredState!, kvp => (string.Equals(kvp.Key, "Override", StringComparison.Ordinal) || string.Equals(kvp.Key, "@Override", StringComparison.Ordinal)) && kvp.Value is not null); } private static (Advisory Vendor, Advisory Registry) CreateVendorAndRegistryAdvisories() { var redHatPublished = new DateTimeOffset(2025, 1, 10, 0, 0, 0, TimeSpan.Zero); var redHatModified = redHatPublished.AddDays(1); var redHatProvenance = new AdvisoryProvenance("redhat", "advisory", "RHSA-2025:0001", redHatModified); var redHatPackage = new AffectedPackage( AffectedPackageTypes.Cpe, "cpe:2.3:o:redhat:enterprise_linux:9:*:*:*:*:*:*:*", "rhel-9", Array.Empty(), new[] { new AffectedPackageStatus("known_affected", redHatProvenance) }, new[] { redHatProvenance }); var redHat = new Advisory( "CVE-2025-1000", "Red Hat Security Advisory", "Vendor-confirmed impact on RHEL 9.", "en", redHatPublished, redHatModified, "high", exploitKnown: false, aliases: new[] { "CVE-2025-1000", "RHSA-2025:0001" }, credits: Array.Empty(), references: new[] { new AdvisoryReference( "https://access.redhat.com/errata/RHSA-2025:0001", "advisory", "redhat", "Red Hat errata", redHatProvenance) }, affectedPackages: new[] { redHatPackage }, cvssMetrics: new[] { new CvssMetric( "3.1", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 9.8, "critical", new AdvisoryProvenance("redhat", "cvss", "RHSA-2025:0001", redHatModified)) }, provenance: new[] { redHatProvenance }); var nvdPublished = new DateTimeOffset(2025, 1, 5, 0, 0, 0, TimeSpan.Zero); var nvdModified = nvdPublished.AddDays(2); var nvdProvenance = new AdvisoryProvenance("nvd", "document", "https://nvd.nist.gov/vuln/detail/CVE-2025-1000", nvdModified); var nvdPackage = new AffectedPackage( AffectedPackageTypes.Cpe, "cpe:2.3:o:redhat:enterprise_linux:9:*:*:*:*:*:*:*", "rhel-9", new[] { new AffectedVersionRange( "cpe", null, null, null, "<=9.0", new AdvisoryProvenance("nvd", "cpe_match", "RHEL", nvdModified)) }, Array.Empty(), new[] { nvdProvenance }); var nvd = new Advisory( "CVE-2025-1000", "CVE-2025-1000", "NVD summary", "en", nvdPublished, nvdModified, "medium", exploitKnown: false, aliases: new[] { "CVE-2025-1000" }, credits: Array.Empty(), references: new[] { new AdvisoryReference( "https://nvd.nist.gov/vuln/detail/CVE-2025-1000", "advisory", "nvd", "NVD advisory", nvdProvenance) }, affectedPackages: new[] { nvdPackage }, cvssMetrics: new[] { new CvssMetric( "3.1", "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N", 6.8, "medium", new AdvisoryProvenance("nvd", "cvss", "CVE-2025-1000", nvdModified)) }, provenance: new[] { nvdProvenance }); return (redHat, nvd); } }