feat(scanner): Implement Deno analyzer and associated tests
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.
This commit is contained in:
master
2025-11-12 10:01:54 +02:00
parent 0e8655cbb1
commit babb81af52
75 changed files with 3346 additions and 187 deletions

View File

@@ -0,0 +1,161 @@
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;
}
}