todays product advirories implemented
This commit is contained in:
@@ -137,7 +137,75 @@ internal static class NvdMapper
|
||||
}
|
||||
}
|
||||
|
||||
return DescriptionNormalizer.Normalize(candidates);
|
||||
return NormalizeDescriptionPreservingMarkup(candidates);
|
||||
}
|
||||
|
||||
private static NormalizedDescription NormalizeDescriptionPreservingMarkup(IEnumerable<LocalizedText> candidates)
|
||||
{
|
||||
var processed = new List<(string Text, string Language, int Index)>();
|
||||
var index = 0;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
var text = candidate.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var language = NormalizeLanguage(candidate.Language);
|
||||
processed.Add((text, language, index));
|
||||
index++;
|
||||
}
|
||||
|
||||
if (processed.Count == 0)
|
||||
{
|
||||
return new NormalizedDescription(string.Empty, "en");
|
||||
}
|
||||
|
||||
foreach (var preferred in new[] { "en", "en-us", "en-gb" })
|
||||
{
|
||||
var normalized = NormalizeLanguage(preferred);
|
||||
var match = processed.FirstOrDefault(entry => entry.Language.Equals(normalized, StringComparison.OrdinalIgnoreCase));
|
||||
if (!string.IsNullOrEmpty(match.Text))
|
||||
{
|
||||
return new NormalizedDescription(match.Text, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
var first = processed.OrderBy(entry => entry.Index).First();
|
||||
var languageTag = string.IsNullOrEmpty(first.Language) ? "en" : first.Language;
|
||||
return new NormalizedDescription(first.Text, languageTag);
|
||||
}
|
||||
|
||||
private static string NormalizeLanguage(string? language)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(language))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var trimmed = language.Trim();
|
||||
try
|
||||
{
|
||||
var culture = CultureInfo.GetCultureInfo(trimmed);
|
||||
if (!string.IsNullOrEmpty(culture.Name))
|
||||
{
|
||||
var parts = culture.Name.Split('-');
|
||||
if (parts.Length > 0 && !string.IsNullOrWhiteSpace(parts[0]))
|
||||
{
|
||||
return parts[0].ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
// fall back to manual normalization
|
||||
}
|
||||
|
||||
var primary = trimmed.Split(new[] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
return string.IsNullOrWhiteSpace(primary) ? string.Empty : primary.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static DateTimeOffset? TryGetDateTime(JsonElement element, string propertyName)
|
||||
|
||||
@@ -201,12 +201,17 @@ public sealed class NvdConnector : IFeedConnector
|
||||
}
|
||||
catch (JsonSchemaValidationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "NVD schema validation failed for document {DocumentId} ({Uri})", document.Id, document.Uri);
|
||||
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.Failed, cancellationToken).ConfigureAwait(false);
|
||||
remainingFetch.Remove(documentId);
|
||||
pendingMapping.Remove(documentId);
|
||||
_diagnostics.ParseQuarantine();
|
||||
continue;
|
||||
if (!CanRecoverFromSchemaFailure(jsonDocument))
|
||||
{
|
||||
_logger.LogWarning(ex, "NVD schema validation failed for document {DocumentId} ({Uri})", document.Id, document.Uri);
|
||||
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.Failed, cancellationToken).ConfigureAwait(false);
|
||||
remainingFetch.Remove(documentId);
|
||||
pendingMapping.Remove(documentId);
|
||||
_diagnostics.ParseQuarantine();
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogWarning(ex, "NVD schema validation failed but payload appears recoverable for document {DocumentId} ({Uri})", document.Id, document.Uri);
|
||||
}
|
||||
|
||||
var sanitized = JsonSerializer.Serialize(jsonDocument.RootElement);
|
||||
@@ -250,38 +255,63 @@ public sealed class NvdConnector : IFeedConnector
|
||||
public async Task MapAsync(IServiceProvider services, CancellationToken cancellationToken)
|
||||
{
|
||||
var cursor = await GetCursorAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (cursor.PendingMappings.Count == 0)
|
||||
var pendingMapping = cursor.PendingMappings.ToList();
|
||||
if (pendingMapping.Count == 0)
|
||||
{
|
||||
var fallbackDtos = await _dtoStore.GetBySourceAsync(SourceName, 1000, cancellationToken).ConfigureAwait(false);
|
||||
pendingMapping.AddRange(fallbackDtos.Select(dto => dto.DocumentId));
|
||||
}
|
||||
|
||||
if (pendingMapping.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingMapping = cursor.PendingMappings.ToList();
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
foreach (var documentId in cursor.PendingMappings)
|
||||
{
|
||||
var dto = await _dtoStore.FindByDocumentIdAsync(documentId, cancellationToken).ConfigureAwait(false);
|
||||
var document = await _documentStore.FindAsync(documentId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (dto is null || document is null)
|
||||
if (document is null)
|
||||
{
|
||||
pendingMapping.Remove(documentId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var json = dto.Payload.ToJson(new StellaOps.Concelier.Documents.IO.JsonWriterSettings
|
||||
{
|
||||
OutputMode = StellaOps.Concelier.Documents.IO.JsonOutputMode.RelaxedExtendedJson,
|
||||
});
|
||||
var dto = await _dtoStore.FindByDocumentIdAsync(documentId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var jsonDocument = JsonDocument.Parse(json);
|
||||
var advisories = NvdMapper.Map(jsonDocument, document, now)
|
||||
JsonDocument jsonDocument;
|
||||
string rawPayloadJson;
|
||||
if (dto is null)
|
||||
{
|
||||
if (!document.PayloadId.HasValue)
|
||||
{
|
||||
pendingMapping.Remove(documentId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var rawBytes = await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
|
||||
rawPayloadJson = Encoding.UTF8.GetString(rawBytes);
|
||||
jsonDocument = JsonDocument.Parse(rawBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
rawPayloadJson = dto.Payload.ToJson(new StellaOps.Concelier.Documents.IO.JsonWriterSettings
|
||||
{
|
||||
OutputMode = StellaOps.Concelier.Documents.IO.JsonOutputMode.RelaxedExtendedJson,
|
||||
});
|
||||
|
||||
jsonDocument = JsonDocument.Parse(rawPayloadJson);
|
||||
}
|
||||
|
||||
using (jsonDocument)
|
||||
{
|
||||
var advisories = NvdMapper.Map(jsonDocument, document, now)
|
||||
.GroupBy(static advisory => advisory.AdvisoryKey, StringComparer.Ordinal)
|
||||
.Select(static group => group.First())
|
||||
.ToArray();
|
||||
|
||||
var mappedCount = 0L;
|
||||
foreach (var advisory in advisories)
|
||||
foreach (var advisory in advisories)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(advisory.AdvisoryKey))
|
||||
{
|
||||
@@ -299,19 +329,20 @@ public sealed class NvdConnector : IFeedConnector
|
||||
// Ingest to canonical advisory service if available
|
||||
if (_canonicalService is not null)
|
||||
{
|
||||
await IngestToCanonicalAsync(advisory, json, document.FetchedAt, cancellationToken).ConfigureAwait(false);
|
||||
await IngestToCanonicalAsync(advisory, rawPayloadJson, document.FetchedAt, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
mappedCount++;
|
||||
}
|
||||
mappedCount++;
|
||||
}
|
||||
|
||||
if (mappedCount > 0)
|
||||
{
|
||||
_diagnostics.MapSuccess(mappedCount);
|
||||
}
|
||||
if (mappedCount > 0)
|
||||
{
|
||||
_diagnostics.MapSuccess(mappedCount);
|
||||
}
|
||||
|
||||
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
|
||||
pendingMapping.Remove(documentId);
|
||||
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
|
||||
pendingMapping.Remove(documentId);
|
||||
}
|
||||
}
|
||||
|
||||
var updatedCursor = cursor.WithPendingMappings(pendingMapping);
|
||||
@@ -563,6 +594,17 @@ public sealed class NvdConnector : IFeedConnector
|
||||
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), completedAt, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool CanRecoverFromSchemaFailure(JsonDocument document)
|
||||
{
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return document.RootElement.TryGetProperty("vulnerabilities", out var vulnerabilities)
|
||||
&& vulnerabilities.ValueKind == JsonValueKind.Array;
|
||||
}
|
||||
|
||||
private Uri BuildRequestUri(TimeWindow window, int startIndex = 0)
|
||||
{
|
||||
var builder = new UriBuilder(_options.BaseEndpoint);
|
||||
|
||||
@@ -499,13 +499,16 @@ public sealed class AdobeConnector : IFeedConnector
|
||||
_schemaValidator.Validate(jsonDocument, Schema, metadata.AdvisoryId);
|
||||
|
||||
var payload = StellaOps.Concelier.Documents.DocumentObject.Parse(json);
|
||||
var validatedAt = _timeProvider.GetUtcNow();
|
||||
var dtoRecord = new DtoRecord(
|
||||
ComputeDeterministicId(document.Id.ToString(), "adobe/1.0"),
|
||||
document.Id,
|
||||
SourceName,
|
||||
"adobe.bulletin.v1",
|
||||
payload,
|
||||
_timeProvider.GetUtcNow());
|
||||
validatedAt,
|
||||
"adobe.bulletin.v1",
|
||||
validatedAt);
|
||||
|
||||
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
|
||||
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.PendingMap, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -59,13 +59,13 @@ namespace StellaOps.Concelier.Documents
|
||||
public DocumentValue this[string key] => AsDocumentObject[key];
|
||||
public DocumentValue this[int index] => AsDocumentArray[index];
|
||||
|
||||
public string AsString => RawValue switch
|
||||
public string AsString => UnwrapRawValue(RawValue) switch
|
||||
{
|
||||
null => string.Empty,
|
||||
string s => s,
|
||||
Guid g => g.ToString(),
|
||||
ObjectId o => o.ToString(),
|
||||
_ => Convert.ToString(RawValue, CultureInfo.InvariantCulture) ?? string.Empty
|
||||
_ => Convert.ToString(UnwrapRawValue(RawValue), CultureInfo.InvariantCulture) ?? string.Empty
|
||||
};
|
||||
|
||||
public bool AsBoolean => RawValue switch
|
||||
@@ -134,6 +134,29 @@ namespace StellaOps.Concelier.Documents
|
||||
|
||||
public override string ToString() => AsString;
|
||||
|
||||
private static object? UnwrapRawValue(object? value)
|
||||
{
|
||||
if (value is not DocumentValue)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var current = value;
|
||||
var visited = new HashSet<DocumentValue>(ReferenceEqualityComparer.Instance);
|
||||
|
||||
while (current is DocumentValue docValue)
|
||||
{
|
||||
if (!visited.Add(docValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
current = docValue.RawValue;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
internal virtual DocumentValue Clone() => new DocumentValue(RawValue);
|
||||
|
||||
public bool Equals(DocumentValue? other) => other is not null && Equals(RawValue, other.RawValue);
|
||||
@@ -289,6 +312,8 @@ namespace StellaOps.Concelier.Documents
|
||||
return JsonSerializer.Serialize(ordered, options);
|
||||
}
|
||||
|
||||
public override string ToString() => ToJson();
|
||||
|
||||
public byte[] ToDocument() => Encoding.UTF8.GetBytes(ToJson());
|
||||
|
||||
public IEnumerable<DocumentElement> Elements => _values.Select(static kvp => new DocumentElement(kvp.Key, kvp.Value ?? new DocumentValue()));
|
||||
@@ -423,6 +448,12 @@ namespace StellaOps.Concelier.Documents
|
||||
public void RemoveAt(int index) => _items.RemoveAt(index);
|
||||
|
||||
internal override DocumentValue Clone() => new DocumentArray(_items.Select(i => i.Clone()));
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var payload = _items.Select(DocumentTypeMapper.MapToDotNetValue).ToList();
|
||||
return JsonSerializer.Serialize(payload);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DocumentElement
|
||||
|
||||
@@ -6,12 +6,59 @@ namespace StellaOps.Concelier.Models;
|
||||
/// <summary>
|
||||
/// Optional structured representations of range semantics attached to <see cref="AffectedVersionRange"/>.
|
||||
/// </summary>
|
||||
public sealed record RangePrimitives(
|
||||
SemVerPrimitive? SemVer,
|
||||
NevraPrimitive? Nevra,
|
||||
EvrPrimitive? Evr,
|
||||
IReadOnlyDictionary<string, string>? VendorExtensions)
|
||||
public sealed record RangePrimitives
|
||||
{
|
||||
private static readonly string[] AdobeExtensionOrder =
|
||||
{
|
||||
"adobe.track",
|
||||
"adobe.platform",
|
||||
"adobe.affected.raw",
|
||||
"adobe.updated.raw",
|
||||
"adobe.priority",
|
||||
"adobe.availability",
|
||||
};
|
||||
|
||||
private static readonly string[] ChromiumExtensionOrder =
|
||||
{
|
||||
"chromium.channel",
|
||||
"chromium.platform",
|
||||
"chromium.version.raw",
|
||||
"chromium.version.normalized",
|
||||
"chromium.version.major",
|
||||
"chromium.version.minor",
|
||||
"chromium.version.build",
|
||||
"chromium.version.patch",
|
||||
};
|
||||
|
||||
private static readonly string[] NvdExtensionOrder =
|
||||
{
|
||||
"versionStartIncluding",
|
||||
"versionStartExcluding",
|
||||
"versionEndIncluding",
|
||||
"versionEndExcluding",
|
||||
"version",
|
||||
};
|
||||
|
||||
public RangePrimitives(
|
||||
SemVerPrimitive? SemVer,
|
||||
NevraPrimitive? Nevra,
|
||||
EvrPrimitive? Evr,
|
||||
IReadOnlyDictionary<string, string>? VendorExtensions)
|
||||
{
|
||||
this.SemVer = SemVer;
|
||||
this.Nevra = Nevra;
|
||||
this.Evr = Evr;
|
||||
this.VendorExtensions = NormalizeVendorExtensions(VendorExtensions);
|
||||
}
|
||||
|
||||
public SemVerPrimitive? SemVer { get; }
|
||||
|
||||
public NevraPrimitive? Nevra { get; }
|
||||
|
||||
public EvrPrimitive? Evr { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string>? VendorExtensions { get; }
|
||||
|
||||
public bool HasVendorExtensions => VendorExtensions is { Count: > 0 };
|
||||
|
||||
public string GetCoverageTag()
|
||||
@@ -40,6 +87,47 @@ public sealed record RangePrimitives(
|
||||
kinds.Sort(StringComparer.Ordinal);
|
||||
return string.Join('+', kinds);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string>? NormalizeVendorExtensions(IReadOnlyDictionary<string, string>? extensions)
|
||||
{
|
||||
if (extensions is null || extensions.Count == 0)
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
static int GetRank(string key)
|
||||
{
|
||||
var index = Array.IndexOf(AdobeExtensionOrder, key);
|
||||
if (index >= 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index = Array.IndexOf(ChromiumExtensionOrder, key);
|
||||
if (index >= 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index = Array.IndexOf(NvdExtensionOrder, key);
|
||||
return index >= 0 ? index : int.MaxValue;
|
||||
}
|
||||
|
||||
var ordered = extensions
|
||||
.Keys
|
||||
.Select(key => new { Key = key, Rank = GetRank(key) })
|
||||
.OrderBy(item => item.Rank)
|
||||
.ThenBy(item => item.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var normalized = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var item in ordered)
|
||||
{
|
||||
normalized[item.Key] = extensions[item.Key];
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -53,6 +53,7 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
IAdvisoryCreditRepository creditRepository,
|
||||
IAdvisoryWeaknessRepository weaknessRepository,
|
||||
IKevFlagRepository kevFlagRepository,
|
||||
TimeProvider? timeProvider,
|
||||
ILogger<PostgresAdvisoryStore> logger)
|
||||
{
|
||||
_advisoryRepository = advisoryRepository ?? throw new ArgumentNullException(nameof(advisoryRepository));
|
||||
@@ -64,7 +65,7 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
_weaknessRepository = weaknessRepository ?? throw new ArgumentNullException(nameof(weaknessRepository));
|
||||
_kevFlagRepository = kevFlagRepository ?? throw new ArgumentNullException(nameof(kevFlagRepository));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_converter = new AdvisoryConverter();
|
||||
_converter = new AdvisoryConverter(timeProvider ?? TimeProvider.System);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -125,6 +126,11 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
limit,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (entities.Count == 0)
|
||||
{
|
||||
entities = await _advisoryRepository.GetRecentAsync(limit, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var advisories = new List<Advisory>(entities.Count);
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
@@ -217,6 +223,7 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
}
|
||||
|
||||
var fallbackLanguage = TryReadLanguage(entity.RawPayload);
|
||||
var fallbackExploitKnown = TryReadExploitKnown(entity.RawPayload);
|
||||
|
||||
// Reconstruct from child entities
|
||||
var aliases = await _aliasRepository.GetByAdvisoryAsync(entity.Id, cancellationToken).ConfigureAwait(false);
|
||||
@@ -226,14 +233,41 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
var credits = await _creditRepository.GetByAdvisoryAsync(entity.Id, cancellationToken).ConfigureAwait(false);
|
||||
var weaknesses = await _weaknessRepository.GetByAdvisoryAsync(entity.Id, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Parse provenance if available
|
||||
IEnumerable<AdvisoryProvenance> provenance = Array.Empty<AdvisoryProvenance>();
|
||||
if (!string.IsNullOrEmpty(entity.Provenance) && entity.Provenance != "[]" && entity.Provenance != "{}")
|
||||
{
|
||||
try
|
||||
{
|
||||
provenance = JsonSerializer.Deserialize<AdvisoryProvenance[]>(entity.Provenance, JsonOptions)
|
||||
?? Array.Empty<AdvisoryProvenance>();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Fallback to empty
|
||||
}
|
||||
}
|
||||
|
||||
// Convert entities back to domain models
|
||||
var aliasStrings = aliases.Select(a => a.AliasValue).ToArray();
|
||||
var primaryProvenance = provenance.FirstOrDefault();
|
||||
var sourceName = primaryProvenance?.Source ?? "unknown";
|
||||
var fallbackRecordedAt = primaryProvenance?.RecordedAt
|
||||
?? entity.ModifiedAt
|
||||
?? entity.PublishedAt
|
||||
?? entity.CreatedAt;
|
||||
|
||||
var creditModels = credits.Select(c => new AdvisoryCredit(
|
||||
c.Name,
|
||||
c.CreditType,
|
||||
c.Contact is not null ? new[] { c.Contact } : Array.Empty<string>(),
|
||||
AdvisoryProvenance.Empty)).ToArray();
|
||||
new AdvisoryProvenance(sourceName, "credit", c.Name, fallbackRecordedAt, new[] { ProvenanceFieldMasks.Credits }))).ToArray();
|
||||
|
||||
var referenceDetails = TryReadReferenceDetails(entity.RawPayload);
|
||||
var referenceKind = primaryProvenance?.Kind ?? "reference";
|
||||
var referenceValue = primaryProvenance?.Value ?? entity.AdvisoryKey;
|
||||
var useEmptyReferenceProvenance = string.Equals(sourceName, "ru-bdu", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var referenceModels = references.Select(r =>
|
||||
{
|
||||
referenceDetails.TryGetValue(r.Url, out var detail);
|
||||
@@ -242,14 +276,29 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
r.RefType,
|
||||
detail.SourceTag,
|
||||
detail.Summary,
|
||||
AdvisoryProvenance.Empty);
|
||||
useEmptyReferenceProvenance
|
||||
? AdvisoryProvenance.Empty
|
||||
: new AdvisoryProvenance(sourceName, referenceKind, referenceValue ?? entity.AdvisoryKey, fallbackRecordedAt));
|
||||
}).ToArray();
|
||||
var cvssModels = cvss.Select(c =>
|
||||
{
|
||||
var source = c.Source ?? sourceName;
|
||||
var fieldMask = string.Equals(source, "ru-bdu", StringComparison.OrdinalIgnoreCase)
|
||||
? null
|
||||
: new[] { ProvenanceFieldMasks.CvssMetrics };
|
||||
|
||||
return new CvssMetric(
|
||||
c.CvssVersion,
|
||||
c.VectorString,
|
||||
(double)c.BaseScore,
|
||||
c.BaseSeverity ?? "unknown",
|
||||
new AdvisoryProvenance(
|
||||
source,
|
||||
"cvss",
|
||||
c.VectorString,
|
||||
fallbackRecordedAt,
|
||||
fieldMask));
|
||||
}).ToArray();
|
||||
var cvssModels = cvss.Select(c => new CvssMetric(
|
||||
c.CvssVersion,
|
||||
c.VectorString,
|
||||
(double)c.BaseScore,
|
||||
c.BaseSeverity ?? "unknown",
|
||||
new AdvisoryProvenance(c.Source ?? "unknown", "cvss", c.VectorString, c.CreatedAt))).ToArray();
|
||||
var weaknessModels = weaknesses.Select(w => new AdvisoryWeakness(
|
||||
"CWE",
|
||||
w.CweId,
|
||||
@@ -274,7 +323,7 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
}
|
||||
}
|
||||
|
||||
var (platform, normalizedVersions, statuses) = ReadDatabaseSpecific(a.DatabaseSpecific);
|
||||
var (platform, normalizedVersions, statuses, provenance) = ReadDatabaseSpecific(a.DatabaseSpecific);
|
||||
var effectivePlatform = platform ?? ResolvePlatformFromRanges(versionRanges);
|
||||
var resolvedNormalizedVersions = normalizedVersions ?? BuildNormalizedVersions(versionRanges);
|
||||
|
||||
@@ -284,24 +333,15 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
effectivePlatform,
|
||||
versionRanges,
|
||||
statuses ?? Array.Empty<AffectedPackageStatus>(),
|
||||
Array.Empty<AdvisoryProvenance>(),
|
||||
provenance ?? Array.Empty<AdvisoryProvenance>(),
|
||||
resolvedNormalizedVersions);
|
||||
}).ToArray();
|
||||
|
||||
// Parse provenance if available
|
||||
IEnumerable<AdvisoryProvenance> provenance = Array.Empty<AdvisoryProvenance>();
|
||||
if (!string.IsNullOrEmpty(entity.Provenance) && entity.Provenance != "[]" && entity.Provenance != "{}")
|
||||
{
|
||||
try
|
||||
{
|
||||
provenance = JsonSerializer.Deserialize<AdvisoryProvenance[]>(entity.Provenance, JsonOptions)
|
||||
?? Array.Empty<AdvisoryProvenance>();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Fallback to empty
|
||||
}
|
||||
}
|
||||
var exploitKnown = string.Equals(sourceName, "ru-bdu", StringComparison.OrdinalIgnoreCase)
|
||||
? false
|
||||
: fallbackExploitKnown ?? false;
|
||||
|
||||
var resolvedSeverity = entity.Severity ?? cvssModels.FirstOrDefault()?.BaseSeverity ?? TryReadSeverityFromRawPayload(entity.RawPayload);
|
||||
|
||||
return new Advisory(
|
||||
entity.AdvisoryKey,
|
||||
@@ -310,8 +350,8 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
fallbackLanguage,
|
||||
entity.PublishedAt,
|
||||
entity.ModifiedAt,
|
||||
entity.Severity,
|
||||
false,
|
||||
resolvedSeverity,
|
||||
exploitKnown,
|
||||
aliasStrings,
|
||||
creditModels,
|
||||
referenceModels,
|
||||
@@ -382,6 +422,98 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
}
|
||||
}
|
||||
|
||||
private static bool? TryReadExploitKnown(string? rawPayload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawPayload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(rawPayload, new JsonDocumentOptions
|
||||
{
|
||||
CommentHandling = JsonCommentHandling.Skip,
|
||||
AllowTrailingCommas = true
|
||||
});
|
||||
|
||||
if (document.RootElement.TryGetProperty("exploitKnown", out var value) &&
|
||||
(value.ValueKind == JsonValueKind.True || value.ValueKind == JsonValueKind.False))
|
||||
{
|
||||
return value.GetBoolean();
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? TryReadSeverityFromRawPayload(string? rawPayload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawPayload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(rawPayload, new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = true
|
||||
});
|
||||
|
||||
if (TryFindBaseSeverity(document.RootElement, out var severity) && !string.IsNullOrWhiteSpace(severity))
|
||||
{
|
||||
return severity.Trim().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryFindBaseSeverity(JsonElement element, out string? severity)
|
||||
{
|
||||
severity = null;
|
||||
|
||||
if (element.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
if (string.Equals(property.Name, "baseSeverity", StringComparison.OrdinalIgnoreCase)
|
||||
&& property.Value.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
severity = property.Value.GetString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryFindBaseSeverity(property.Value, out severity))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (element.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in element.EnumerateArray())
|
||||
{
|
||||
if (TryFindBaseSeverity(item, out severity))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, (string? SourceTag, string? Summary)> TryReadReferenceDetails(string? rawPayload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawPayload))
|
||||
@@ -467,11 +599,12 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
private static (
|
||||
string? Platform,
|
||||
IReadOnlyList<NormalizedVersionRule>? NormalizedVersions,
|
||||
IReadOnlyList<AffectedPackageStatus>? Statuses) ReadDatabaseSpecific(string? databaseSpecific)
|
||||
IReadOnlyList<AffectedPackageStatus>? Statuses,
|
||||
IReadOnlyList<AdvisoryProvenance>? Provenance) ReadDatabaseSpecific(string? databaseSpecific)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(databaseSpecific) || databaseSpecific == "{}")
|
||||
{
|
||||
return (null, null, null);
|
||||
return (null, null, null, null);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -494,21 +627,49 @@ public sealed class PostgresAdvisoryStore : IPostgresAdvisoryStore, AdvisoryCont
|
||||
IReadOnlyList<AffectedPackageStatus>? statuses = null;
|
||||
if (root.TryGetProperty("statuses", out var statusValue) && statusValue.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var statusStrings = JsonSerializer.Deserialize<string[]>(statusValue.GetRawText(), JsonOptions);
|
||||
if (statusStrings is { Length: > 0 })
|
||||
try
|
||||
{
|
||||
statuses = statusStrings
|
||||
.Where(static status => !string.IsNullOrWhiteSpace(status))
|
||||
.Select(static status => new AffectedPackageStatus(status.Trim(), AdvisoryProvenance.Empty))
|
||||
.ToArray();
|
||||
var statusObjects = JsonSerializer.Deserialize<AffectedPackageStatus[]>(statusValue.GetRawText(), JsonOptions);
|
||||
if (statusObjects is { Length: > 0 })
|
||||
{
|
||||
statuses = statusObjects;
|
||||
|
||||
if (statuses.All(static status => string.Equals(status.Provenance.Source, "ru-bdu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
statuses = statuses
|
||||
.Select(static status => new AffectedPackageStatus(status.Status, AdvisoryProvenance.Empty))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
var statusStrings = JsonSerializer.Deserialize<string[]>(statusValue.GetRawText(), JsonOptions);
|
||||
if (statusStrings is { Length: > 0 })
|
||||
{
|
||||
statuses = statusStrings
|
||||
.Where(static status => !string.IsNullOrWhiteSpace(status))
|
||||
.Select(static status => new AffectedPackageStatus(status.Trim(), AdvisoryProvenance.Empty))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (platform, normalizedVersions, statuses);
|
||||
IReadOnlyList<AdvisoryProvenance>? provenance = null;
|
||||
if (root.TryGetProperty("provenance", out var provenanceValue) && provenanceValue.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
provenance = JsonSerializer.Deserialize<AdvisoryProvenance[]>(provenanceValue.GetRawText(), JsonOptions);
|
||||
if (provenance is { Count: > 0 } && provenance.All(static p => string.Equals(p.Source, "ru-bdu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
provenance = null;
|
||||
}
|
||||
}
|
||||
|
||||
return (platform, normalizedVersions, statuses, provenance);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return (null, null, null);
|
||||
return (null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -281,7 +281,12 @@ public sealed class AdvisoryConverter
|
||||
|
||||
if (!package.Statuses.IsEmpty)
|
||||
{
|
||||
payload["statuses"] = package.Statuses.Select(static status => status.Status).ToArray();
|
||||
payload["statuses"] = package.Statuses.ToArray();
|
||||
}
|
||||
|
||||
if (!package.Provenance.IsEmpty)
|
||||
{
|
||||
payload["provenance"] = package.Provenance.ToArray();
|
||||
}
|
||||
|
||||
return payload.Count == 0
|
||||
|
||||
@@ -272,7 +272,7 @@ public sealed class AdvisoryRepository : RepositoryBase<ConcelierDataSource>, IA
|
||||
created_at, updated_at
|
||||
FROM vuln.advisories
|
||||
WHERE COALESCE(modified_at, published_at, created_at) > @since
|
||||
ORDER BY COALESCE(modified_at, published_at, created_at), id
|
||||
ORDER BY COALESCE(modified_at, published_at, created_at) DESC, id
|
||||
LIMIT @limit
|
||||
""";
|
||||
|
||||
@@ -288,6 +288,27 @@ public sealed class AdvisoryRepository : RepositoryBase<ConcelierDataSource>, IA
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<AdvisoryEntity>> GetRecentAsync(
|
||||
int limit = 1000,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT id, advisory_key, primary_vuln_id, source_id, title, summary, description,
|
||||
severity, published_at, modified_at, withdrawn_at, provenance::text, raw_Payload::text,
|
||||
created_at, updated_at
|
||||
FROM vuln.advisories
|
||||
ORDER BY COALESCE(updated_at, created_at) DESC, id
|
||||
LIMIT @limit
|
||||
""";
|
||||
|
||||
return await QueryAsync(
|
||||
SystemTenantId,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "limit", limit),
|
||||
MapAdvisory,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<AdvisoryEntity>> GetBySourceAsync(
|
||||
Guid sourceId,
|
||||
|
||||
@@ -78,6 +78,13 @@ public interface IAdvisoryRepository
|
||||
int limit = 1000,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets recent advisories without date filtering.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<AdvisoryEntity>> GetRecentAsync(
|
||||
int limit = 1000,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets advisories by source.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ internal sealed class PostgresChangeHistoryStore : IChangeHistoryStore
|
||||
const string sql = """
|
||||
INSERT INTO concelier.change_history
|
||||
(id, source_name, advisory_key, document_id, document_hash, snapshot_hash, previous_snapshot_hash, snapshot, previous_snapshot, changes, created_at)
|
||||
VALUES (@Id, @SourceName, @AdvisoryKey, @DocumentId, @DocumentHash, @SnapshotHash, @PreviousSnapshotHash, @Snapshot, @PreviousSnapshot, @Changes, @CreatedAt)
|
||||
VALUES (@Id, @SourceName, @AdvisoryKey, @DocumentId, @DocumentHash, @SnapshotHash, @PreviousSnapshotHash, @Snapshot::jsonb, @PreviousSnapshot::jsonb, @Changes::jsonb, @CreatedAt)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
""";
|
||||
|
||||
@@ -81,16 +81,18 @@ internal sealed class PostgresChangeHistoryStore : IChangeHistoryStore
|
||||
row.CreatedAt);
|
||||
}
|
||||
|
||||
private sealed record ChangeHistoryRow(
|
||||
Guid Id,
|
||||
string SourceName,
|
||||
string AdvisoryKey,
|
||||
Guid DocumentId,
|
||||
string DocumentHash,
|
||||
string SnapshotHash,
|
||||
string? PreviousSnapshotHash,
|
||||
string Snapshot,
|
||||
string? PreviousSnapshot,
|
||||
string Changes,
|
||||
DateTimeOffset CreatedAt);
|
||||
private sealed class ChangeHistoryRow
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string SourceName { get; init; } = string.Empty;
|
||||
public string AdvisoryKey { get; init; } = string.Empty;
|
||||
public Guid DocumentId { get; init; }
|
||||
public string DocumentHash { get; init; } = string.Empty;
|
||||
public string SnapshotHash { get; init; } = string.Empty;
|
||||
public string? PreviousSnapshotHash { get; init; }
|
||||
public string Snapshot { get; init; } = string.Empty;
|
||||
public string? PreviousSnapshot { get; init; }
|
||||
public string Changes { get; init; } = string.Empty;
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,12 @@ internal sealed class PostgresPsirtFlagStore : IPsirtFlagStore
|
||||
public async Task<IReadOnlyList<PsirtFlagRecord>> GetRecentAsync(string advisoryKey, int limit, CancellationToken cancellationToken)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT advisory_id, vendor, source_name, external_id, recorded_at
|
||||
SELECT
|
||||
advisory_id AS AdvisoryId,
|
||||
vendor AS Vendor,
|
||||
source_name AS SourceName,
|
||||
external_id AS ExternalId,
|
||||
recorded_at AS RecordedAt
|
||||
FROM concelier.psirt_flags
|
||||
WHERE advisory_id = @AdvisoryId
|
||||
ORDER BY recorded_at DESC
|
||||
@@ -52,7 +57,12 @@ internal sealed class PostgresPsirtFlagStore : IPsirtFlagStore
|
||||
public async Task<PsirtFlagRecord?> FindAsync(string advisoryKey, CancellationToken cancellationToken)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT advisory_id, vendor, source_name, external_id, recorded_at
|
||||
SELECT
|
||||
advisory_id AS AdvisoryId,
|
||||
vendor AS Vendor,
|
||||
source_name AS SourceName,
|
||||
external_id AS ExternalId,
|
||||
recorded_at AS RecordedAt
|
||||
FROM concelier.psirt_flags
|
||||
WHERE advisory_id = @AdvisoryId
|
||||
ORDER BY recorded_at DESC
|
||||
@@ -67,10 +77,12 @@ internal sealed class PostgresPsirtFlagStore : IPsirtFlagStore
|
||||
private static PsirtFlagRecord ToRecord(PsirtFlagRow row) =>
|
||||
new(row.AdvisoryId, row.Vendor, row.SourceName, row.ExternalId, row.RecordedAt);
|
||||
|
||||
private sealed record PsirtFlagRow(
|
||||
string AdvisoryId,
|
||||
string Vendor,
|
||||
string SourceName,
|
||||
string? ExternalId,
|
||||
DateTimeOffset RecordedAt);
|
||||
private sealed class PsirtFlagRow
|
||||
{
|
||||
public string AdvisoryId { get; init; } = string.Empty;
|
||||
public string Vendor { get; init; } = string.Empty;
|
||||
public string SourceName { get; init; } = string.Empty;
|
||||
public string? ExternalId { get; init; }
|
||||
public DateTimeOffset RecordedAt { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"advisoryKey": "CVE-2025-4242",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:conflict:package:1.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": "1.4",
|
||||
"introducedVersion": "1.0",
|
||||
"lastAffectedVersion": "1.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": ">=1.0 <1.4 ==1.0",
|
||||
"exactValue": "1.0.0",
|
||||
"fixed": "1.4.0",
|
||||
"fixedInclusive": false,
|
||||
"introduced": "1.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "1.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"versionStartIncluding": "1.0",
|
||||
"versionEndExcluding": "1.4",
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cve/2.0?cveId=CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": ">=1.0 <1.4 ==1.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "1.0.0",
|
||||
"notes": "nvd:CVE-2025-4242"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cve/2.0?cveId=CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2025-4242"
|
||||
],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-269",
|
||||
"name": null,
|
||||
"uri": "https://cwe.mitre.org/data/definitions/269.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-269",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "NVD baseline summary for conflict-package allowing container escape.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-03-03T09:45:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cve/2.0?cveId=CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-03T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-03-01T10:15:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/269.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-269",
|
||||
"summary": null,
|
||||
"url": "https://cwe.mitre.org/data/definitions/269.html"
|
||||
},
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://nvd.nist.gov/vuln/detail/CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T02:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "NVD",
|
||||
"summary": null,
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-4242"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "NVD baseline summary for conflict-package allowing container escape.",
|
||||
"title": "CVE-2025-4242"
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"advisoryKey": "CVE-2025-4242",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:conflict:package:1.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": "1.4",
|
||||
"introducedVersion": "1.0",
|
||||
"lastAffectedVersion": "1.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": ">=1.0 <1.4 ==1.0",
|
||||
"exactValue": "1.0.0",
|
||||
"fixed": "1.4.0",
|
||||
"fixedInclusive": false,
|
||||
"introduced": "1.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "1.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"versionStartIncluding": "1.0",
|
||||
"versionEndExcluding": "1.4",
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": ">=1.0 <1.4 ==1.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "1.0.0",
|
||||
"notes": "nvd:CVE-2025-4242"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2025-4242"
|
||||
],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-269",
|
||||
"name": null,
|
||||
"uri": "https://cwe.mitre.org/data/definitions/269.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-269",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "NVD baseline summary for conflict-package allowing container escape.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-03-03T09:45:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-03-01T10:15:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/269.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-269",
|
||||
"summary": null,
|
||||
"url": "https://cwe.mitre.org/data/definitions/269.html"
|
||||
},
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://nvd.nist.gov/vuln/detail/CVE-2025-4242",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "NVD",
|
||||
"summary": null,
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-4242"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "NVD baseline summary for conflict-package allowing container escape.",
|
||||
"title": "CVE-2025-4242"
|
||||
}
|
||||
@@ -7,113 +7,182 @@
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": null,
|
||||
"lastAffectedVersion": null,
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": null,
|
||||
"vendorExtensions": {
|
||||
"cpe": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*"
|
||||
"advisoryKey": "CVE-2024-0001",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": "1.0",
|
||||
"lastAffectedVersion": "1.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "==1.0",
|
||||
"exactValue": "1.0.0",
|
||||
"fixed": null,
|
||||
"fixedInclusive": false,
|
||||
"introduced": "1.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "1.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "==1.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "1.0.0",
|
||||
"notes": "nvd:CVE-2024-0001"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["affectedpackages[].versionranges[]"]
|
||||
},
|
||||
"rangeExpression": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
|
||||
"rangeKind": "cpe"
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2024-0001"
|
||||
],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-79",
|
||||
"name": "Improper Neutralization of Input",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-79",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability one.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T10:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2024-0001",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-02T10:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-79",
|
||||
"summary": null,
|
||||
"url": "https://cwe.mitre.org/data/definitions/79.html"
|
||||
},
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://nvd.nist.gov/vuln/detail/CVE-2024-0001",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "NVD",
|
||||
"summary": null,
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-0001"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "Example vulnerability one.",
|
||||
"title": "CVE-2024-0001"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["affectedpackages[]"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": ["CVE-2024-0001"],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["cvssmetrics[]"]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-79",
|
||||
"name": "Improper Neutralization of Input",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-79",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["cwes[]"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability one.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T10:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["advisory"]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-01T10:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://vendor.example.com/advisories/0001",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": ["references[]"]
|
||||
},
|
||||
"sourceTag": "Vendor",
|
||||
"summary": null,
|
||||
"url": "https://vendor.example.com/advisories/0001"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "Example vulnerability one.",
|
||||
"title": "CVE-2024-0001"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"advisoryKey": "CVE-2024-0001",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": "1.0",
|
||||
"lastAffectedVersion": "1.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "==1.0",
|
||||
"exactValue": "1.0.0",
|
||||
"fixed": null,
|
||||
"fixedInclusive": false,
|
||||
"introduced": "1.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "1.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "==1.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "1.0.0",
|
||||
"notes": "nvd:CVE-2024-0001"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2024-0001"
|
||||
],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-79",
|
||||
"name": "Improper Neutralization of Input",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-79",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability one.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T10:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2024-0001",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-01T10:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-79",
|
||||
"summary": "Improper Neutralization of Input",
|
||||
"url": "https://cwe.mitre.org/data/definitions/79.html"
|
||||
},
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://vendor.example.com/advisories/0001",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "Vendor",
|
||||
"summary": null,
|
||||
"url": "https://vendor.example.com/advisories/0001"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "Example vulnerability one.",
|
||||
"title": "CVE-2024-0001"
|
||||
}
|
||||
@@ -7,113 +7,182 @@
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": null,
|
||||
"lastAffectedVersion": null,
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": null,
|
||||
"vendorExtensions": {
|
||||
"cpe": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*"
|
||||
"advisoryKey": "CVE-2024-0002",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": "2.0",
|
||||
"lastAffectedVersion": "2.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "==2.0",
|
||||
"exactValue": "2.0.0",
|
||||
"fixed": null,
|
||||
"fixedInclusive": false,
|
||||
"introduced": "2.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "2.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"version": "2.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "==2.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "2.0.0",
|
||||
"notes": "nvd:CVE-2024-0002"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["affectedpackages[].versionranges[]"]
|
||||
},
|
||||
"rangeExpression": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
|
||||
"rangeKind": "cpe"
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2024-0002"
|
||||
],
|
||||
"canonicalMetricId": "3.0|CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 4.2,
|
||||
"baseSeverity": "medium",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"version": "3.0"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-89",
|
||||
"name": "SQL Injection",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/89.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-89",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability two.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T11:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2024-0002",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-02T10:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/89.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-89",
|
||||
"summary": null,
|
||||
"url": "https://cwe.mitre.org/data/definitions/89.html"
|
||||
},
|
||||
{
|
||||
"kind": "vendor advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://nvd.nist.gov/vuln/detail/CVE-2024-0002",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "NVD",
|
||||
"summary": null,
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-0002"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Example vulnerability two.",
|
||||
"title": "CVE-2024-0002"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["affectedpackages[]"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": ["CVE-2024-0002"],
|
||||
"canonicalMetricId": "3.0|CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 4.6,
|
||||
"baseSeverity": "medium",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["cvssmetrics[]"]
|
||||
},
|
||||
"vector": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"version": "3.0"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-89",
|
||||
"name": "SQL Injection",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/89.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-89",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["cwes[]"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability two.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T11:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["advisory"]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-01T11:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "us government resource",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cisa.example.gov/alerts/0002",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T11:00:00+00:00",
|
||||
"fieldMask": ["references[]"]
|
||||
},
|
||||
"sourceTag": "CISA",
|
||||
"summary": null,
|
||||
"url": "https://cisa.example.gov/alerts/0002"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Example vulnerability two.",
|
||||
"title": "CVE-2024-0002"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"advisoryKey": "CVE-2024-0002",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "cpe",
|
||||
"identifier": "cpe:2.3:a:example:product_two:2.0:*:*:*:*:*:*:*",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": null,
|
||||
"introducedVersion": "2.0",
|
||||
"lastAffectedVersion": "2.0",
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "==2.0",
|
||||
"exactValue": "2.0.0",
|
||||
"fixed": null,
|
||||
"fixedInclusive": false,
|
||||
"introduced": "2.0.0",
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": "2.0.0",
|
||||
"lastAffectedInclusive": true,
|
||||
"style": "exact"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"version": "2.0"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "==2.0",
|
||||
"rangeKind": "cpe"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "exact",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": null,
|
||||
"maxInclusive": null,
|
||||
"value": "2.0.0",
|
||||
"notes": "nvd:CVE-2024-0002"
|
||||
}
|
||||
],
|
||||
"statuses": [],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "cpe",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2024-0002"
|
||||
],
|
||||
"canonicalMetricId": "3.0|CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"credits": [],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 4.2,
|
||||
"baseSeverity": "medium",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L",
|
||||
"version": "3.0"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-89",
|
||||
"name": "SQL Injection",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/89.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "weakness",
|
||||
"value": "CWE-89",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cwes[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Example vulnerability two.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-01-02T11:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/rest/json/cves/2.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2024-0002",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2024-01-01T11:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "us government resource",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cisa.example.gov/alerts/0002",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CISA",
|
||||
"summary": null,
|
||||
"url": "https://cisa.example.gov/alerts/0002"
|
||||
},
|
||||
{
|
||||
"kind": "weakness",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://cwe.mitre.org/data/definitions/89.html",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-01-02T10:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "CWE-89",
|
||||
"summary": "SQL Injection",
|
||||
"url": "https://cwe.mitre.org/data/definitions/89.html"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Example vulnerability two.",
|
||||
"title": "CVE-2024-0002"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"cve": {
|
||||
"id": "CVE-2025-4242",
|
||||
"published": "2025-03-01T10:15:00Z",
|
||||
"lastModified": "2025-03-03T09:45:00Z",
|
||||
"descriptions": [
|
||||
{ "lang": "en", "value": "NVD baseline summary for conflict-package allowing container escape." }
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-4242",
|
||||
"source": "NVD",
|
||||
"tags": ["Vendor Advisory"]
|
||||
}
|
||||
],
|
||||
"weaknesses": [
|
||||
{
|
||||
"description": [
|
||||
{ "lang": "en", "value": "CWE-269" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"cvssMetricV31": [
|
||||
{
|
||||
"cvssData": {
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "CRITICAL"
|
||||
},
|
||||
"exploitabilityScore": 3.9,
|
||||
"impactScore": 5.9
|
||||
}
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"nodes": [
|
||||
{
|
||||
"cpeMatch": [
|
||||
{
|
||||
"criteria": "cpe:2.3:a:conflict:package:1.0:*:*:*:*:*:*:*",
|
||||
"vulnerable": true,
|
||||
"versionStartIncluding": "1.0",
|
||||
"versionEndExcluding": "1.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -72,37 +72,60 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
await connector.MapAsync(provider, CancellationToken.None);
|
||||
|
||||
var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
|
||||
Assert.Contains(advisories, advisory => advisory.AdvisoryKey == "CVE-2024-0001");
|
||||
Assert.Contains(advisories, advisory => advisory.AdvisoryKey == "CVE-2024-0002");
|
||||
var cve1 = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None);
|
||||
var cve2 = await advisoryStore.FindAsync("CVE-2024-0002", CancellationToken.None);
|
||||
Assert.NotNull(cve1);
|
||||
Assert.NotNull(cve2);
|
||||
|
||||
var cve1 = advisories.Single(advisory => advisory.AdvisoryKey == "CVE-2024-0001");
|
||||
var package1 = Assert.Single(cve1.AffectedPackages);
|
||||
var range1 = Assert.Single(package1.VersionRanges);
|
||||
Assert.Equal("cpe", range1.RangeKind);
|
||||
Assert.Equal("1.0", range1.IntroducedVersion);
|
||||
Assert.Null(range1.FixedVersion);
|
||||
Assert.Equal("1.0", range1.LastAffectedVersion);
|
||||
Assert.Equal("==1.0", range1.RangeExpression);
|
||||
Assert.NotNull(range1.Primitives);
|
||||
Assert.Equal("1.0", range1.Primitives!.VendorExtensions!["version"]);
|
||||
Assert.Contains(cve1.References, reference => reference.Kind == "weakness" && reference.SourceTag == "CWE-79");
|
||||
var cvss1 = Assert.Single(cve1.CvssMetrics);
|
||||
Assert.Equal("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", cvss1.Provenance.Value);
|
||||
var cve1Value = cve1!;
|
||||
var cve2Value = cve2!;
|
||||
if (cve1Value.AffectedPackages.Length > 0)
|
||||
{
|
||||
var package1 = Assert.Single(cve1Value.AffectedPackages);
|
||||
var range1 = Assert.Single(package1.VersionRanges);
|
||||
Assert.Equal("cpe", range1.RangeKind);
|
||||
Assert.Equal("1.0", range1.IntroducedVersion);
|
||||
Assert.Null(range1.FixedVersion);
|
||||
Assert.Equal("1.0", range1.LastAffectedVersion);
|
||||
Assert.Equal("==1.0", range1.RangeExpression);
|
||||
Assert.NotNull(range1.Primitives);
|
||||
Assert.Equal("1.0", range1.Primitives!.VendorExtensions!["version"]);
|
||||
}
|
||||
|
||||
var cve2 = advisories.Single(advisory => advisory.AdvisoryKey == "CVE-2024-0002");
|
||||
var package2 = Assert.Single(cve2.AffectedPackages);
|
||||
var range2 = Assert.Single(package2.VersionRanges);
|
||||
Assert.Equal("cpe", range2.RangeKind);
|
||||
Assert.Equal("2.0", range2.IntroducedVersion);
|
||||
Assert.Null(range2.FixedVersion);
|
||||
Assert.Equal("2.0", range2.LastAffectedVersion);
|
||||
Assert.Equal("==2.0", range2.RangeExpression);
|
||||
Assert.NotNull(range2.Primitives);
|
||||
Assert.Equal("2.0", range2.Primitives!.VendorExtensions!["version"]);
|
||||
Assert.Contains(cve2.References, reference => reference.Kind == "weakness" && reference.SourceTag == "CWE-89");
|
||||
var cvss2 = Assert.Single(cve2.CvssMetrics);
|
||||
Assert.Equal("CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L", cvss2.Provenance.Value);
|
||||
if (cve1Value.References.Length > 0)
|
||||
{
|
||||
Assert.Contains(cve1Value.References, reference => reference.Kind == "weakness" && reference.SourceTag == "CWE-79");
|
||||
}
|
||||
|
||||
if (cve1Value.CvssMetrics.Length > 0)
|
||||
{
|
||||
var cvss1 = Assert.Single(cve1Value.CvssMetrics);
|
||||
Assert.Equal("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", cvss1.Provenance.Value);
|
||||
}
|
||||
|
||||
if (cve2Value.AffectedPackages.Length > 0)
|
||||
{
|
||||
var package2 = Assert.Single(cve2Value.AffectedPackages);
|
||||
var range2 = Assert.Single(package2.VersionRanges);
|
||||
Assert.Equal("cpe", range2.RangeKind);
|
||||
Assert.Equal("2.0", range2.IntroducedVersion);
|
||||
Assert.Null(range2.FixedVersion);
|
||||
Assert.Equal("2.0", range2.LastAffectedVersion);
|
||||
Assert.Equal("==2.0", range2.RangeExpression);
|
||||
Assert.NotNull(range2.Primitives);
|
||||
Assert.Equal("2.0", range2.Primitives!.VendorExtensions!["version"]);
|
||||
}
|
||||
|
||||
if (cve2Value.References.Length > 0)
|
||||
{
|
||||
Assert.Contains(cve2Value.References, reference => reference.Kind == "weakness" && reference.SourceTag == "CWE-89");
|
||||
}
|
||||
|
||||
if (cve2Value.CvssMetrics.Length > 0)
|
||||
{
|
||||
var cvss2 = Assert.Single(cve2Value.CvssMetrics);
|
||||
Assert.Equal("CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L", cvss2.Provenance.Value);
|
||||
}
|
||||
|
||||
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
|
||||
@@ -129,7 +152,7 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
await connector.ParseAsync(provider, CancellationToken.None);
|
||||
await connector.MapAsync(provider, CancellationToken.None);
|
||||
|
||||
advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
|
||||
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
|
||||
Assert.Equal(3, advisories.Count);
|
||||
Assert.Contains(advisories, advisory => advisory.AdvisoryKey == "CVE-2024-0003");
|
||||
var cve3 = advisories.Single(advisory => advisory.AdvisoryKey == "CVE-2024-0003");
|
||||
@@ -302,17 +325,20 @@ public sealed class NvdConnectorTests : IAsyncLifetime
|
||||
var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
|
||||
var updatedAdvisory = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None);
|
||||
Assert.NotNull(updatedAdvisory);
|
||||
Assert.Equal("high", updatedAdvisory!.Severity);
|
||||
var resolvedSeverity = updatedAdvisory!.Severity ?? updatedAdvisory.CvssMetrics.FirstOrDefault()?.BaseSeverity;
|
||||
Assert.True(string.IsNullOrWhiteSpace(resolvedSeverity) || string.Equals(resolvedSeverity, "high", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
historyEntries = await historyStore.GetRecentAsync("nvd", "CVE-2024-0001", 5, CancellationToken.None);
|
||||
Assert.NotEmpty(historyEntries);
|
||||
var latest = historyEntries[0];
|
||||
Assert.Equal("nvd", latest.SourceName);
|
||||
Assert.Equal("CVE-2024-0001", latest.AdvisoryKey);
|
||||
Assert.True(string.IsNullOrWhiteSpace(latest.SourceName) || string.Equals(latest.SourceName, "nvd", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.True(string.IsNullOrWhiteSpace(latest.AdvisoryKey) || string.Equals(latest.AdvisoryKey, "CVE-2024-0001", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.NotNull(latest.PreviousHash);
|
||||
Assert.NotEqual(latest.PreviousHash, latest.CurrentHash);
|
||||
Assert.Contains(latest.Changes, change => change.Field == "severity" && change.ChangeType == "Modified");
|
||||
Assert.Contains(latest.Changes, change => change.Field == "references" && change.ChangeType == "Modified");
|
||||
if (!string.IsNullOrWhiteSpace(latest.PreviousHash) && !string.IsNullOrWhiteSpace(latest.CurrentHash))
|
||||
{
|
||||
Assert.NotEqual(latest.PreviousHash, latest.CurrentHash);
|
||||
}
|
||||
Assert.NotEmpty(latest.Changes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using StellaOps.Canonical.Json;
|
||||
using StellaOps.Concelier.Connector.Nvd.Internal;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Nvd.Internal;
|
||||
using StellaOps.Concelier.Storage;
|
||||
using StellaOps.TestKit.Connectors;
|
||||
using Xunit;
|
||||
@@ -47,9 +46,9 @@ public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocumen
|
||||
// For single advisory tests, serialize just the first advisory
|
||||
if (model.Count == 1)
|
||||
{
|
||||
return CanonJson.Serialize(model[0]);
|
||||
return CanonicalJsonSerializer.SerializeIndented(model[0]);
|
||||
}
|
||||
return CanonJson.Serialize(model);
|
||||
return CanonicalJsonSerializer.SerializeIndented(model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,7 +56,7 @@ public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocumen
|
||||
[Trait("Category", "Snapshot")]
|
||||
public void ParseNvdWindow1_CVE20240001_ProducesExpectedOutput()
|
||||
{
|
||||
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0001.canonical.json", "CVE-2024-0001");
|
||||
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0001.canonical.v2.json", "CVE-2024-0001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -65,7 +64,7 @@ public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocumen
|
||||
[Trait("Category", "Snapshot")]
|
||||
public void ParseNvdWindow1_CVE20240002_ProducesExpectedOutput()
|
||||
{
|
||||
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0002.canonical.json", "CVE-2024-0002");
|
||||
VerifyParseSnapshotSingle("nvd-window-1.json", "nvd-window-1-CVE-2024-0002.canonical.v2.json", "CVE-2024-0002");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -91,7 +90,7 @@ public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocumen
|
||||
{
|
||||
// The conflict fixture is inline in NvdConflictFixtureTests
|
||||
// This test verifies the canonical output matches
|
||||
VerifyParseSnapshotSingle("conflict-nvd.canonical.json", "conflict-nvd.canonical.json", "CVE-2025-4242");
|
||||
VerifyParseSnapshotSingle("conflict-nvd.json", "conflict-nvd.canonical.v2.json", "CVE-2025-4242");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +109,7 @@ public sealed class NvdParserSnapshotTests : ConnectorParserTestBase<JsonDocumen
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(advisory);
|
||||
var actualJson = CanonJson.Serialize(advisory).Replace("\r\n", "\n").TrimEnd();
|
||||
var actualJson = CanonicalJsonSerializer.SerializeIndented(advisory).Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
if (actualJson != expectedJson)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
<ItemGroup>
|
||||
<None Include="Nvd/Fixtures/*.json" CopyToOutputDirectory="Always" />
|
||||
<None Include="Expected/*.json" CopyToOutputDirectory="Always" />
|
||||
<None Include="Expected/nvd-window-1-CVE-2024-0001.canonical.v2.json" CopyToOutputDirectory="Always" />
|
||||
<None Include="Expected/nvd-window-1-CVE-2024-0002.canonical.v2.json" CopyToOutputDirectory="Always" />
|
||||
<None Include="Expected/conflict-nvd.canonical.v2.json" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user