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,150 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using MongoDB.Driver;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Storage.Mongo;
using StellaOps.Excititor.WebService.Services;
using Xunit;
namespace StellaOps.Excititor.WebService.Tests;
public sealed class VexObservationProjectionServiceTests
{
[Fact]
public async Task QueryAsync_FiltersByProviderAndStatus()
{
var now = new DateTimeOffset(2025, 11, 10, 12, 0, 0, TimeSpan.Zero);
var claims = new[]
{
CreateClaim("provider-a", VexClaimStatus.Affected, now.AddHours(-6), now.AddHours(-5)),
CreateClaim("provider-b", VexClaimStatus.NotAffected, now.AddHours(-4), now.AddHours(-3))
};
var store = new FakeClaimStore(claims);
var service = new VexObservationProjectionService(store, new FixedTimeProvider(now));
var request = new VexObservationProjectionRequest(
Tenant: "tenant-a",
VulnerabilityId: "CVE-2025-0001",
ProductKey: "pkg:docker/demo",
ProviderIds: ImmutableHashSet.Create("provider-b"),
Statuses: ImmutableHashSet.Create(VexClaimStatus.NotAffected),
Since: null,
Limit: 10);
var result = await service.QueryAsync(request, CancellationToken.None);
result.Truncated.Should().BeFalse();
result.TotalCount.Should().Be(1);
result.GeneratedAtUtc.Should().Be(now);
var statement = result.Statements.Single();
statement.ProviderId.Should().Be("provider-b");
statement.Status.Should().Be(VexClaimStatus.NotAffected);
statement.Justification.Should().Be(VexJustification.ComponentNotPresent);
statement.Anchors.Should().ContainSingle().Which.Should().Be("/statements/0");
statement.Scope.ComponentIdentifiers.Should().Contain("demo:component");
statement.Document.Digest.Should().Contain("provider-b");
}
[Fact]
public async Task QueryAsync_TruncatesWhenLimitExceeded()
{
var now = DateTimeOffset.UtcNow;
var claims = Enumerable.Range(0, 3)
.Select(index => CreateClaim($"provider-{index}", VexClaimStatus.NotAffected, now.AddHours(-index - 2), now.AddHours(-index - 1)))
.ToArray();
var store = new FakeClaimStore(claims);
var service = new VexObservationProjectionService(store, new FixedTimeProvider(now));
var request = new VexObservationProjectionRequest(
Tenant: "tenant-a",
VulnerabilityId: "CVE-2025-0001",
ProductKey: "pkg:docker/demo",
ProviderIds: ImmutableHashSet<string>.Empty,
Statuses: ImmutableHashSet<VexClaimStatus>.Empty,
Since: null,
Limit: 2);
var result = await service.QueryAsync(request, CancellationToken.None);
result.Truncated.Should().BeTrue();
result.TotalCount.Should().Be(3);
result.Statements.Should().HaveCount(2);
}
private static VexClaim CreateClaim(string providerId, VexClaimStatus status, DateTimeOffset firstSeen, DateTimeOffset lastSeen)
{
var product = new VexProduct(
key: "pkg:docker/demo",
name: "demo",
version: "1.0.0",
purl: "pkg:docker/demo@1.0.0",
cpe: "cpe:/a:demo:demo:1.0.0",
componentIdentifiers: new[] { "demo:component" });
var document = new VexClaimDocument(
VexDocumentFormat.Csaf,
$"sha256:{providerId}",
new Uri("https://example.org/vex.json"),
revision: "v1");
var metadata = ImmutableDictionary<string, string>.Empty.Add("json_pointer", "/statements/0");
return new VexClaim(
"CVE-2025-0001",
providerId,
product,
status,
document,
firstSeen,
lastSeen,
justification: VexJustification.ComponentNotPresent,
detail: "not affected",
confidence: null,
signals: null,
additionalMetadata: metadata);
}
private sealed class FakeClaimStore : IVexClaimStore
{
private readonly IReadOnlyCollection<VexClaim> _claims;
public FakeClaimStore(IReadOnlyCollection<VexClaim> claims)
{
_claims = claims;
}
public ValueTask AppendAsync(IEnumerable<VexClaim> claims, DateTimeOffset observedAt, CancellationToken cancellationToken, IClientSessionHandle? session = null)
=> throw new NotSupportedException();
public ValueTask<IReadOnlyCollection<VexClaim>> FindAsync(string vulnerabilityId, string productKey, DateTimeOffset? since, CancellationToken cancellationToken, IClientSessionHandle? session = null)
{
var query = _claims
.Where(claim => string.Equals(claim.VulnerabilityId, vulnerabilityId, StringComparison.OrdinalIgnoreCase))
.Where(claim => string.Equals(claim.Product.Key, productKey, StringComparison.OrdinalIgnoreCase));
if (since.HasValue)
{
query = query.Where(claim => claim.LastSeen >= since.Value);
}
return ValueTask.FromResult<IReadOnlyCollection<VexClaim>>(query.ToList());
}
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _timestamp;
public FixedTimeProvider(DateTimeOffset timestamp)
{
_timestamp = timestamp;
}
public override DateTimeOffset GetUtcNow() => _timestamp;
}
}