Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added Deno analyzer with comprehensive metadata and evidence structure. - Created a detailed implementation plan for Sprint 130 focusing on Deno analyzer. - Introduced AdvisoryAiGuardrailOptions for managing guardrail configurations. - Developed GuardrailPhraseLoader for loading blocked phrases from JSON files. - Implemented tests for AdvisoryGuardrailOptions binding and phrase loading. - Enhanced telemetry for Advisory AI with metrics tracking. - Added VexObservationProjectionService for querying VEX observations. - Created extensive tests for VexObservationProjectionService functionality. - Introduced Ruby language analyzer with tests for simple and complex workspaces. - Added Ruby application fixtures for testing purposes.
162 lines
5.0 KiB
C#
162 lines
5.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using StellaOps.Excititor.Core;
|
|
|
|
namespace StellaOps.Excititor.WebService.Services;
|
|
|
|
internal interface IVexObservationProjectionService
|
|
{
|
|
Task<VexObservationProjectionResult> QueryAsync(
|
|
VexObservationProjectionRequest request,
|
|
CancellationToken cancellationToken);
|
|
}
|
|
|
|
internal sealed record VexObservationProjectionRequest(
|
|
string Tenant,
|
|
string VulnerabilityId,
|
|
string ProductKey,
|
|
ImmutableHashSet<string> ProviderIds,
|
|
ImmutableHashSet<VexClaimStatus> Statuses,
|
|
DateTimeOffset? Since,
|
|
int Limit);
|
|
|
|
internal sealed record VexObservationProjectionResult(
|
|
IReadOnlyList<VexObservationStatementProjection> Statements,
|
|
bool Truncated,
|
|
int TotalCount,
|
|
DateTimeOffset GeneratedAtUtc);
|
|
|
|
internal sealed record VexObservationStatementProjection(
|
|
string ObservationId,
|
|
string ProviderId,
|
|
VexClaimStatus Status,
|
|
VexJustification? Justification,
|
|
string? Detail,
|
|
DateTimeOffset FirstSeen,
|
|
DateTimeOffset LastSeen,
|
|
VexProductScope Scope,
|
|
IReadOnlyList<string> Anchors,
|
|
VexClaimDocument Document,
|
|
VexSignatureMetadata? Signature);
|
|
|
|
internal sealed record VexProductScope(
|
|
string Key,
|
|
string? Name,
|
|
string? Version,
|
|
string? Purl,
|
|
string? Cpe,
|
|
IReadOnlyList<string> ComponentIdentifiers);
|
|
|
|
internal sealed class VexObservationProjectionService : IVexObservationProjectionService
|
|
{
|
|
private static readonly string[] AnchorKeys =
|
|
{
|
|
"json_pointer",
|
|
"jsonPointer",
|
|
"statement_locator",
|
|
"locator",
|
|
"paragraph",
|
|
"section",
|
|
"path"
|
|
};
|
|
|
|
private readonly IVexClaimStore _claimStore;
|
|
private readonly TimeProvider _timeProvider;
|
|
|
|
public VexObservationProjectionService(IVexClaimStore claimStore, TimeProvider? timeProvider = null)
|
|
{
|
|
_claimStore = claimStore ?? throw new ArgumentNullException(nameof(claimStore));
|
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
|
}
|
|
|
|
public async Task<VexObservationProjectionResult> QueryAsync(
|
|
VexObservationProjectionRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
var claims = await _claimStore.FindAsync(
|
|
request.VulnerabilityId,
|
|
request.ProductKey,
|
|
request.Since,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
var filtered = claims
|
|
.Where(claim => MatchesProvider(claim, request.ProviderIds))
|
|
.Where(claim => MatchesStatus(claim, request.Statuses))
|
|
.OrderByDescending(claim => claim.LastSeen)
|
|
.ThenBy(claim => claim.ProviderId, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
var total = filtered.Count;
|
|
var page = filtered.Take(request.Limit).ToList();
|
|
var statements = page
|
|
.Select(claim => MapClaim(claim))
|
|
.ToList();
|
|
|
|
return new VexObservationProjectionResult(
|
|
statements,
|
|
total > request.Limit,
|
|
total,
|
|
_timeProvider.GetUtcNow());
|
|
}
|
|
|
|
private static bool MatchesProvider(VexClaim claim, ImmutableHashSet<string> providers)
|
|
=> providers.Count == 0 || providers.Contains(claim.ProviderId, StringComparer.OrdinalIgnoreCase);
|
|
|
|
private static bool MatchesStatus(VexClaim claim, ImmutableHashSet<VexClaimStatus> statuses)
|
|
=> statuses.Count == 0 || statuses.Contains(claim.Status);
|
|
|
|
private static VexObservationStatementProjection MapClaim(VexClaim claim)
|
|
{
|
|
var observationId = string.Create(CultureInfo.InvariantCulture, $"{claim.ProviderId}:{claim.Document.Digest}");
|
|
var anchors = ExtractAnchors(claim.AdditionalMetadata);
|
|
var scope = new VexProductScope(
|
|
claim.Product.Key,
|
|
claim.Product.Name,
|
|
claim.Product.Version,
|
|
claim.Product.Purl,
|
|
claim.Product.Cpe,
|
|
claim.Product.ComponentIdentifiers);
|
|
|
|
return new VexObservationStatementProjection(
|
|
observationId,
|
|
claim.ProviderId,
|
|
claim.Status,
|
|
claim.Justification,
|
|
claim.Detail,
|
|
claim.FirstSeen,
|
|
claim.LastSeen,
|
|
scope,
|
|
anchors,
|
|
claim.Document,
|
|
claim.Document.Signature);
|
|
}
|
|
|
|
private static IReadOnlyList<string> ExtractAnchors(ImmutableSortedDictionary<string, string> metadata)
|
|
{
|
|
if (metadata.Count == 0)
|
|
{
|
|
return Array.Empty<string>();
|
|
}
|
|
|
|
var anchors = new List<string>();
|
|
foreach (var key in AnchorKeys)
|
|
{
|
|
if (metadata.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
|
|
{
|
|
anchors.Add(value.Trim());
|
|
}
|
|
}
|
|
|
|
return anchors.Count == 0 ? Array.Empty<string>() : anchors;
|
|
}
|
|
}
|