audit, advisories and doctors/setup work

This commit is contained in:
master
2026-01-13 18:53:39 +02:00
parent 9ca7cb183e
commit d7be6ba34b
811 changed files with 54242 additions and 4056 deletions

View File

@@ -21,7 +21,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_WithValidDocument_ReturnsSuccessResult()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -47,7 +47,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_ProjectsToRepository()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -71,7 +71,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_SetsScanStatusToCompleted()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -93,7 +93,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_WithEmptyDocument_ReturnsZeroCounts()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -125,7 +125,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_WithNullDocument_ThrowsArgumentNullException()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -141,7 +141,7 @@ public sealed class CallGraphSyncServiceTests
public async Task SyncAsync_WithEmptyArtifactDigest_ThrowsArgumentException()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,
@@ -159,7 +159,7 @@ public sealed class CallGraphSyncServiceTests
public async Task DeleteByScanAsync_RemovesScanFromRepository()
{
// Arrange
var repository = new InMemoryCallGraphProjectionRepository();
var repository = new InMemoryCallGraphProjectionRepository(TimeProvider.System);
var service = new CallGraphSyncService(
repository,
TimeProvider.System,

View File

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Parsing;
@@ -46,6 +47,7 @@ public class CallgraphIngestionServiceTests
callGraphSyncService,
options,
_timeProvider,
SystemGuidProvider.Instance,
NullLogger<CallgraphIngestionService>.Instance);
var artifactJson = @"{""nodes"":[{""id"":""com/example/Foo.bar:(I)V"",""kind"":""fn""}],

View File

@@ -5,6 +5,7 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Determinism;
using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
@@ -24,7 +25,11 @@ public class EdgeBundleIngestionServiceTests
var opts = new SignalsOptions();
opts.Storage.RootPath = Path.GetTempPath();
var options = Microsoft.Extensions.Options.Options.Create(opts);
_service = new EdgeBundleIngestionService(NullLogger<EdgeBundleIngestionService>.Instance, options);
_service = new EdgeBundleIngestionService(
NullLogger<EdgeBundleIngestionService>.Instance,
options,
TimeProvider.System,
SystemGuidProvider.Instance);
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;
@@ -30,7 +30,7 @@ public class EvidenceWeightedScoreCalculatorTests
var result = _calculator.Calculate(input, _defaultPolicy);
// Without MIT, sum of weights = 0.95 (default) 95%
// Without MIT, sum of weights = 0.95 (default) -> 95%
result.Score.Should().BeGreaterThanOrEqualTo(90);
result.Bucket.Should().Be(ScoreBucket.ActNow);
}

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Configuration;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;
@@ -290,7 +290,7 @@ public class NormalizerAggregatorTests
};
var optionsMonitor = new TestOptionsMonitor(options);
var aggregator = new NormalizerAggregator(optionsMonitor);
var aggregator = new NormalizerAggregator(optionsMonitor, TimeProvider.System);
var evidence = new FindingEvidence
{

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
@@ -15,7 +15,7 @@ namespace StellaOps.Signals.Tests.EvidenceWeightedScore.Normalizers;
/// </summary>
public class NormalizerIntegrationTests
{
#region Backport Evidence BKP Score Tests
#region Backport Evidence -> BKP Score Tests
[Fact]
public void BackportEvidence_PatchSignatureFixed_ProducesHighBkpScore()
@@ -80,7 +80,7 @@ public class NormalizerIntegrationTests
#endregion
#region EPSS + KEV XPL Score Tests
#region EPSS + KEV -> XPL Score Tests
[Fact]
public void ExploitEvidence_HighEpssAndKev_ProducesHighXplScore()
@@ -147,7 +147,7 @@ public class NormalizerIntegrationTests
#endregion
#region Full Evidence Pipeline Score Input Tests
#region Full Evidence Pipeline -> Score Input Tests
[Fact]
public void FullEvidence_AllDimensions_ProducesValidScoreInput()

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using StellaOps.Signals.EvidenceWeightedScore;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;
@@ -19,7 +19,7 @@ public class RuntimeSignalNormalizerTests
public RuntimeSignalNormalizerTests()
{
_sut = new RuntimeSignalNormalizer(_defaultOptions);
_sut = new RuntimeSignalNormalizer(_defaultOptions, TimeProvider.System);
}
#region Dimension Property Tests
@@ -517,7 +517,7 @@ public class RuntimeSignalNormalizerTests
};
var optionsMonitor = new TestOptionsMonitor(options);
var normalizer = new RuntimeSignalNormalizer(optionsMonitor);
var normalizer = new RuntimeSignalNormalizer(optionsMonitor, TimeProvider.System);
var input = new RuntimeInput
{

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright © 2025 StellaOps
// Copyright (c) 2025 StellaOps
using FluentAssertions;
using Microsoft.Extensions.Options;

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
@@ -22,7 +23,7 @@ public class InMemoryEventsPublisherTests
options.Events.Stream = "signals.fact.updated.v1";
options.Events.DefaultTenant = "tenant-default";
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System);
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System, SystemGuidProvider.Instance);
var publisher = new InMemoryEventsPublisher(logger, builder);
var fact = new ReachabilityFactDocument

View File

@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Persistence;
@@ -64,6 +65,7 @@ public class ReachabilityScoringServiceTests
callgraphRepository,
factRepository,
TimeProvider.System,
SystemGuidProvider.Instance,
Options.Create(options),
cache,
unknowns,
@@ -135,6 +137,7 @@ public class ReachabilityScoringServiceTests
callgraphRepository,
factRepository,
TimeProvider.System,
SystemGuidProvider.Instance,
Options.Create(options),
cache,
unknowns,
@@ -220,6 +223,7 @@ public class ReachabilityScoringServiceTests
callgraphRepository,
factRepository,
TimeProvider.System,
SystemGuidProvider.Instance,
Options.Create(options),
cache,
unknowns,

View File

@@ -6,6 +6,7 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
@@ -25,7 +26,7 @@ public class RouterEventsPublisherTests
var handler = new StubHandler(HttpStatusCode.Accepted);
using var httpClient = new HttpClient(handler) { BaseAddress = new Uri(options.Events.Router.BaseUrl) };
var logger = new ListLogger<RouterEventsPublisher>();
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System);
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System, SystemGuidProvider.Instance);
var publisher = new RouterEventsPublisher(builder, options, httpClient, logger);
await publisher.PublishFactUpdatedAsync(CreateFact(), CancellationToken.None);
@@ -49,7 +50,7 @@ public class RouterEventsPublisherTests
var handler = new StubHandler(HttpStatusCode.InternalServerError, "boom");
using var httpClient = new HttpClient(handler) { BaseAddress = new Uri(options.Events.Router.BaseUrl) };
var logger = new ListLogger<RouterEventsPublisher>();
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System);
var builder = new ReachabilityFactEventBuilder(options, TimeProvider.System, SystemGuidProvider.Instance);
var publisher = new RouterEventsPublisher(builder, options, httpClient, logger);
await publisher.PublishFactUpdatedAsync(CreateFact(), CancellationToken.None);

View File

@@ -3,6 +3,7 @@ using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Cryptography;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
@@ -207,6 +208,7 @@ public class RuntimeFactsBatchIngestionTests
return new RuntimeFactsIngestionService(
repository,
TimeProvider.System,
SystemGuidProvider.Instance,
cache,
eventsPublisher,
scoringService,

View File

@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
@@ -25,6 +26,7 @@ public class RuntimeFactsIngestionServiceTests
var service = new RuntimeFactsIngestionService(
factRepository,
TimeProvider.System,
SystemGuidProvider.Instance,
cache,
eventsPublisher,
scoringService,
@@ -446,6 +448,7 @@ public class RuntimeFactsIngestionServiceTests
return new RuntimeFactsIngestionService(
factRepository,
TimeProvider.System,
SystemGuidProvider.Instance,
new InMemoryReachabilityCache(),
new RecordingEventsPublisher(),
new RecordingScoringService(),

View File

@@ -0,0 +1,163 @@
// <copyright file="ScmWebhookServiceTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// </copyright>
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Signals.Options;
using StellaOps.Signals.Scm.Models;
using StellaOps.Signals.Scm.Services;
using StellaOps.Signals.Scm.Webhooks;
using Xunit;
namespace StellaOps.Signals.Tests.Scm;
/// <summary>
/// Unit tests for SCM webhook processing.
/// </summary>
public sealed class ScmWebhookServiceTests
{
private static readonly DateTimeOffset FixedTimestamp =
DateTimeOffset.Parse("2025-01-01T00:00:00Z", CultureInfo.InvariantCulture);
[Fact]
public async Task ProcessAsync_RejectsWhenSecretMissingAndUnsignedNotAllowed()
{
var options = new SignalsOptions();
options.Scm.AllowUnsignedWebhooks = false;
var triggerService = new TestScmTriggerService();
var service = CreateService(options, triggerService);
var payload = Encoding.UTF8.GetBytes("{}");
var result = await service.ProcessAsync(
ScmProvider.GitHub,
eventType: "push",
deliveryId: "delivery-1",
signature: null,
payload: payload,
integrationId: null,
tenantId: null);
Assert.False(result.Success);
Assert.Equal(401, result.StatusCode);
Assert.Equal(0, triggerService.Calls);
}
[Fact]
public async Task ProcessAsync_AllowsUnsignedWhenConfigured()
{
var options = new SignalsOptions();
options.Scm.AllowUnsignedWebhooks = true;
var triggerService = new TestScmTriggerService();
var service = CreateService(options, triggerService);
var payload = Encoding.UTF8.GetBytes("{}");
var result = await service.ProcessAsync(
ScmProvider.GitHub,
eventType: "push",
deliveryId: "delivery-2",
signature: null,
payload: payload,
integrationId: "integration-1",
tenantId: "tenant-1");
Assert.True(result.Success);
Assert.Equal(202, result.StatusCode);
Assert.Equal(1, triggerService.Calls);
Assert.Equal("integration-1", result.Event?.IntegrationId);
Assert.Equal("tenant-1", result.Event?.TenantId);
}
[Fact]
public async Task ProcessAsync_ValidSignature_AcceptsAndDispatches()
{
var options = new SignalsOptions();
options.Scm.DefaultSecret = "test-secret";
var triggerService = new TestScmTriggerService();
var service = CreateService(options, triggerService);
var payload = Encoding.UTF8.GetBytes("{}");
var signature = ComputeGitHubSignature(payload, options.Scm.DefaultSecret);
var result = await service.ProcessAsync(
ScmProvider.GitHub,
eventType: "push",
deliveryId: "delivery-3",
signature: signature,
payload: payload,
integrationId: null,
tenantId: null);
Assert.True(result.Success);
Assert.Equal(202, result.StatusCode);
Assert.Equal(1, triggerService.Calls);
Assert.Equal("delivery-3", result.Event?.EventId);
}
private static ScmWebhookService CreateService(SignalsOptions options, TestScmTriggerService triggerService)
{
return new ScmWebhookService(
NullLogger<ScmWebhookService>.Instance,
Options.Create(options),
triggerService,
new IWebhookSignatureValidator[] { new GitHubWebhookValidator() },
new IScmEventMapper[] { new TestScmEventMapper(ScmProvider.GitHub) });
}
private static string ComputeGitHubSignature(byte[] payload, string secret)
{
var secretBytes = Encoding.UTF8.GetBytes(secret);
var hash = HMACSHA256.HashData(secretBytes, payload);
return $"sha256={Convert.ToHexStringLower(hash)}";
}
private sealed class TestScmEventMapper : IScmEventMapper
{
public TestScmEventMapper(ScmProvider provider)
{
Provider = provider;
}
public ScmProvider Provider { get; }
public NormalizedScmEvent? Map(string eventType, string deliveryId, JsonElement payload)
{
return new NormalizedScmEvent
{
EventId = deliveryId,
Provider = Provider,
EventType = ScmEventType.Push,
Timestamp = FixedTimestamp,
Repository = new ScmRepository
{
FullName = "stellaops/signals"
}
};
}
}
private sealed class TestScmTriggerService : IScmTriggerService
{
public int Calls { get; private set; }
public Task<ScmTriggerResult> ProcessEventAsync(NormalizedScmEvent scmEvent, CancellationToken cancellationToken = default)
{
Calls++;
return Task.FromResult(new ScmTriggerResult
{
TriggersDispatched = true,
ScanTriggersCount = 1,
SbomTriggersCount = 0
});
}
}
}

View File

@@ -29,7 +29,7 @@ public class UnknownsDecayServiceTests
_timeProvider = new MockTimeProvider(new DateTimeOffset(2025, 12, 15, 12, 0, 0, TimeSpan.Zero));
_unknownsRepo = new InMemoryUnknownsRepository();
_deploymentRefs = new InMemoryDeploymentRefsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository(TimeProvider.System);
_scoringOptions = new UnknownsScoringOptions();
_decayOptions = new UnknownsDecayOptions();
}

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Determinism;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
@@ -17,7 +18,7 @@ public class UnknownsIngestionServiceTests
public async Task IngestAsync_StoresNormalizedUnknowns()
{
var repo = new InMemoryUnknownsRepository();
var service = new UnknownsIngestionService(repo, TimeProvider.System, NullLogger<UnknownsIngestionService>.Instance);
var service = new UnknownsIngestionService(repo, TimeProvider.System, SystemGuidProvider.Instance, NullLogger<UnknownsIngestionService>.Instance);
var request = new UnknownsIngestRequest
{
@@ -49,7 +50,7 @@ public class UnknownsIngestionServiceTests
public async Task IngestAsync_ThrowsWhenEmpty()
{
var repo = new InMemoryUnknownsRepository();
var service = new UnknownsIngestionService(repo, TimeProvider.System, NullLogger<UnknownsIngestionService>.Instance);
var service = new UnknownsIngestionService(repo, TimeProvider.System, SystemGuidProvider.Instance, NullLogger<UnknownsIngestionService>.Instance);
var request = new UnknownsIngestRequest
{

View File

@@ -17,7 +17,7 @@ namespace StellaOps.Signals.Tests;
/// <summary>
/// Integration tests for the unknowns scoring system.
/// Tests end-to-end flow: ingest score persist query.
/// Tests end-to-end flow: ingest -> score -> persist -> query.
/// </summary>
public sealed class UnknownsScoringIntegrationTests
{
@@ -32,7 +32,7 @@ public sealed class UnknownsScoringIntegrationTests
_timeProvider = new MockTimeProvider(new DateTimeOffset(2025, 12, 15, 12, 0, 0, TimeSpan.Zero));
_unknownsRepo = new FullInMemoryUnknownsRepository(_timeProvider);
_deploymentRefs = new InMemoryDeploymentRefsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository(TimeProvider.System);
_defaultOptions = new UnknownsScoringOptions();
}

View File

@@ -27,7 +27,7 @@ public class UnknownsScoringServiceTests
_timeProvider = new MockTimeProvider(new DateTimeOffset(2025, 12, 15, 12, 0, 0, TimeSpan.Zero));
_unknownsRepo = new InMemoryUnknownsRepository();
_deploymentRefs = new InMemoryDeploymentRefsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository();
_graphMetrics = new InMemoryGraphMetricsRepository(TimeProvider.System);
_defaultOptions = new UnknownsScoringOptions();
}