Files
git.stella-ops.org/src/StellaOps.Concelier.Core/Unknown/UnknownStateLedger.cs
2025-10-21 18:54:26 +03:00

314 lines
9.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Concelier.Models;
namespace StellaOps.Concelier.Core.Unknown;
/// <summary>
/// Default implementation that derives unknown-state markers from canonical advisories.
/// </summary>
public sealed class UnknownStateLedger : IUnknownStateLedger
{
private static readonly ImmutableHashSet<string> ImpactStatuses = ImmutableHashSet.Create(
StringComparer.Ordinal,
AffectedPackageStatusCatalog.KnownAffected,
AffectedPackageStatusCatalog.Affected,
AffectedPackageStatusCatalog.UnderInvestigation,
AffectedPackageStatusCatalog.Pending,
AffectedPackageStatusCatalog.Unknown);
private static readonly ImmutableHashSet<string> FixStatuses = ImmutableHashSet.Create(
StringComparer.Ordinal,
AffectedPackageStatusCatalog.Fixed,
AffectedPackageStatusCatalog.FirstFixed,
AffectedPackageStatusCatalog.Mitigated);
private static readonly ImmutableDictionary<string, UnknownMarkerSeed> MarkerSeeds = new Dictionary<string, UnknownMarkerSeed>(StringComparer.Ordinal)
{
[UnknownStateMarkerKinds.UnknownVulnerabilityRange] = new UnknownMarkerSeed(0.8, "high"),
[UnknownStateMarkerKinds.UnknownOrigin] = new UnknownMarkerSeed(0.6, "medium"),
[UnknownStateMarkerKinds.AmbiguousFix] = new UnknownMarkerSeed(0.45, "medium"),
}.ToImmutableDictionary(StringComparer.Ordinal);
private readonly IUnknownStateRepository _repository;
private readonly TimeProvider _timeProvider;
public UnknownStateLedger(IUnknownStateRepository repository, TimeProvider? timeProvider = null)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async ValueTask<UnknownStateLedgerResult> RecordAsync(
UnknownStateLedgerRequest request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
var recordedAt = _timeProvider.GetUtcNow();
var markers = EvaluateMarkers(request.Advisory, request.AsOf, recordedAt);
await _repository.UpsertAsync(request.VulnerabilityKey, markers, cancellationToken).ConfigureAwait(false);
return new UnknownStateLedgerResult(request.VulnerabilityKey, request.AsOf, markers);
}
public ValueTask<IReadOnlyList<UnknownStateSnapshot>> GetByVulnerabilityAsync(
string vulnerabilityKey,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(vulnerabilityKey);
var normalizedKey = vulnerabilityKey.Trim().ToLowerInvariant();
return _repository.GetByVulnerabilityAsync(normalizedKey, cancellationToken);
}
private static ImmutableArray<UnknownStateSnapshot> EvaluateMarkers(
Advisory advisory,
DateTimeOffset observedAt,
DateTimeOffset recordedAt)
{
var builder = ImmutableArray.CreateBuilder<UnknownStateSnapshot>(initialCapacity: 3);
if (advisory is not null)
{
if (TryCreateUnknownVulnerabilityRangeMarker(advisory, observedAt, recordedAt, out var unknownRange))
{
builder.Add(unknownRange);
}
if (TryCreateUnknownOriginMarker(advisory, observedAt, recordedAt, out var unknownOrigin))
{
builder.Add(unknownOrigin);
}
if (TryCreateAmbiguousFixMarker(advisory, observedAt, recordedAt, out var ambiguousFix))
{
builder.Add(ambiguousFix);
}
}
if (builder.Count == 0)
{
return ImmutableArray<UnknownStateSnapshot>.Empty;
}
builder.Sort(static (left, right) => StringComparer.Ordinal.Compare(left.Marker, right.Marker));
return builder.ToImmutable();
}
private static bool TryCreateUnknownVulnerabilityRangeMarker(
Advisory advisory,
DateTimeOffset observedAt,
DateTimeOffset recordedAt,
out UnknownStateSnapshot snapshot)
{
snapshot = null!;
if (advisory.AffectedPackages.IsDefaultOrEmpty)
{
return false;
}
var lackingPackages = 0;
foreach (var package in advisory.AffectedPackages)
{
if (package is null)
{
continue;
}
if (!HasImpactStatus(package))
{
continue;
}
if (!HasConcreteRange(package))
{
lackingPackages++;
}
}
if (lackingPackages == 0)
{
return false;
}
var seed = MarkerSeeds[UnknownStateMarkerKinds.UnknownVulnerabilityRange];
var evidence = lackingPackages == 1
? "1 affected package lacks explicit version ranges."
: $"{lackingPackages} affected packages lack explicit version ranges.";
snapshot = new UnknownStateSnapshot(
UnknownStateMarkerKinds.UnknownVulnerabilityRange,
seed.Confidence,
seed.Band,
observedAt,
recordedAt,
evidence);
return true;
}
private static bool TryCreateUnknownOriginMarker(
Advisory advisory,
DateTimeOffset observedAt,
DateTimeOffset recordedAt,
out UnknownStateSnapshot snapshot)
{
snapshot = null!;
if (ContainsKnownProvenance(advisory.Provenance))
{
return false;
}
var seed = MarkerSeeds[UnknownStateMarkerKinds.UnknownOrigin];
var evidence = advisory.Provenance.IsDefaultOrEmpty
? "Advisory provenance is missing; falling back to inferred sources."
: "All advisory provenance sources resolve to 'unknown'.";
snapshot = new UnknownStateSnapshot(
UnknownStateMarkerKinds.UnknownOrigin,
seed.Confidence,
seed.Band,
observedAt,
recordedAt,
evidence);
return true;
}
private static bool TryCreateAmbiguousFixMarker(
Advisory advisory,
DateTimeOffset observedAt,
DateTimeOffset recordedAt,
out UnknownStateSnapshot snapshot)
{
snapshot = null!;
if (advisory.AffectedPackages.IsDefaultOrEmpty)
{
return false;
}
var ambiguousPackages = 0;
foreach (var package in advisory.AffectedPackages)
{
if (package is null)
{
continue;
}
if (!package.Statuses.IsDefaultOrEmpty && package.Statuses.Any(status => FixStatuses.Contains(status.Status)))
{
var hasFixedRange = package.VersionRanges.Any(static range => !string.IsNullOrWhiteSpace(range.FixedVersion));
if (!hasFixedRange)
{
ambiguousPackages++;
}
}
}
if (ambiguousPackages == 0)
{
return false;
}
var seed = MarkerSeeds[UnknownStateMarkerKinds.AmbiguousFix];
var evidence = ambiguousPackages == 1
? "Fix status published without explicit fixed version details."
: $"Fix status published without explicit fixed versions for {ambiguousPackages} packages.";
snapshot = new UnknownStateSnapshot(
UnknownStateMarkerKinds.AmbiguousFix,
seed.Confidence,
seed.Band,
observedAt,
recordedAt,
evidence);
return true;
}
private static bool HasImpactStatus(AffectedPackage package)
{
if (package.Statuses.IsDefaultOrEmpty)
{
return false;
}
foreach (var status in package.Statuses)
{
if (status is null)
{
continue;
}
if (ImpactStatuses.Contains(status.Status))
{
return true;
}
}
return false;
}
private static bool HasConcreteRange(AffectedPackage package)
{
if (package.VersionRanges.IsDefaultOrEmpty)
{
return false;
}
foreach (var range in package.VersionRanges)
{
if (range is null)
{
continue;
}
if (!string.IsNullOrWhiteSpace(range.IntroducedVersion) ||
!string.IsNullOrWhiteSpace(range.FixedVersion) ||
!string.IsNullOrWhiteSpace(range.LastAffectedVersion) ||
!string.IsNullOrWhiteSpace(range.RangeExpression))
{
return true;
}
}
return false;
}
private static bool ContainsKnownProvenance(ImmutableArray<AdvisoryProvenance> provenance)
{
if (provenance.IsDefaultOrEmpty)
{
return false;
}
foreach (var entry in provenance)
{
if (entry is null)
{
continue;
}
if (IsKnownSource(entry.Source))
{
return true;
}
}
return false;
}
private static bool IsKnownSource(string? source)
=> !string.IsNullOrWhiteSpace(source) &&
!string.Equals(source, "unknown", StringComparison.OrdinalIgnoreCase);
private readonly record struct UnknownMarkerSeed(double Confidence, string Band);
}