part #2
This commit is contained in:
@@ -27,17 +27,21 @@ public static class BinaryIndexServiceExtensions
|
||||
.GetSection("BinaryIndex")
|
||||
.Get<BinaryIndexOptions>() ?? new BinaryIndexOptions();
|
||||
|
||||
services.AddSingleton(options);
|
||||
|
||||
if (!options.Enabled)
|
||||
{
|
||||
services.AddSingleton<IBinaryVulnerabilityService, NullBinaryVulnerabilityService>();
|
||||
services.AddSingleton<IBinaryFeatureExtractor, NullBinaryFeatureExtractor>();
|
||||
services.AddSingleton<BinaryVulnerabilityAnalyzer>();
|
||||
services.AddSingleton<Processing.BinaryFindingMapper>();
|
||||
return services;
|
||||
}
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddScoped<IBinaryVulnerabilityService, BinaryVulnerabilityService>();
|
||||
services.AddScoped<IBinaryFeatureExtractor, ElfFeatureExtractor>();
|
||||
services.AddScoped<BinaryVulnerabilityAnalyzer>();
|
||||
services.AddScoped<Processing.BinaryFindingMapper>();
|
||||
services.AddSingleton<IBinaryVulnerabilityService, BinaryVulnerabilityService>();
|
||||
services.AddSingleton<IBinaryFeatureExtractor, ElfFeatureExtractor>();
|
||||
services.AddSingleton<BinaryVulnerabilityAnalyzer>();
|
||||
services.AddSingleton<Processing.BinaryFindingMapper>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -51,7 +55,7 @@ public sealed class BinaryIndexOptions
|
||||
/// <summary>
|
||||
/// Whether binary vulnerability analysis is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
public bool Enabled { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Batch size for binary lookups.
|
||||
@@ -159,3 +163,27 @@ internal sealed class NullBinaryVulnerabilityService : IBinaryVulnerabilityServi
|
||||
return Task.FromResult(System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<CorpusFunctionMatch>>.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IBinaryFeatureExtractor for when binary analysis is disabled.
|
||||
/// </summary>
|
||||
internal sealed class NullBinaryFeatureExtractor : IBinaryFeatureExtractor
|
||||
{
|
||||
public bool CanExtract(Stream stream) => false;
|
||||
|
||||
public Task<StellaOps.BinaryIndex.Core.Models.BinaryIdentity> ExtractIdentityAsync(Stream stream, CancellationToken ct = default)
|
||||
=> Task.FromResult(new StellaOps.BinaryIndex.Core.Models.BinaryIdentity
|
||||
{
|
||||
BinaryKey = "null",
|
||||
FileSha256 = "",
|
||||
Format = StellaOps.BinaryIndex.Core.Models.BinaryFormat.Elf,
|
||||
Architecture = "unknown"
|
||||
});
|
||||
|
||||
public Task<BinaryMetadata> ExtractMetadataAsync(Stream stream, CancellationToken ct = default)
|
||||
=> Task.FromResult(new BinaryMetadata
|
||||
{
|
||||
Format = StellaOps.BinaryIndex.Core.Models.BinaryFormat.Elf,
|
||||
Architecture = "unknown"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using StellaOps.Scanner.Core.Epss;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Processing;
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IEpssProvider for when EPSS storage is not configured.
|
||||
/// </summary>
|
||||
internal sealed class NullEpssProvider : IEpssProvider
|
||||
{
|
||||
public Task<EpssEvidence?> GetCurrentAsync(string cveId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<EpssEvidence?>(null);
|
||||
|
||||
public Task<EpssBatchResult> GetCurrentBatchAsync(IEnumerable<string> cveIds, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new EpssBatchResult { Found = [], NotFound = cveIds.ToList(), ModelDate = DateOnly.MinValue });
|
||||
|
||||
public Task<EpssEvidence?> GetAsOfDateAsync(string cveId, DateOnly asOfDate, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<EpssEvidence?>(null);
|
||||
|
||||
public Task<IReadOnlyList<EpssEvidence>> GetHistoryAsync(string cveId, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<EpssEvidence>>([]);
|
||||
|
||||
public Task<DateOnly?> GetLatestModelDateAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<DateOnly?>(null);
|
||||
|
||||
public Task<bool> IsAvailableAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(false);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Copyright (c) StellaOps
|
||||
// Null implementations for PoE/Reachability infrastructure dependencies
|
||||
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using StellaOps.Scanner.Storage.ObjectStore;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Processing;
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IRichGraphStore for environments without richgraph infrastructure.
|
||||
/// </summary>
|
||||
internal sealed class NullRichGraphStore : IRichGraphStore
|
||||
{
|
||||
public Task<RichGraphV1?> FetchGraphAsync(string graphHash, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<RichGraphV1?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IEntryPointResolver for environments without entry point resolution.
|
||||
/// </summary>
|
||||
internal sealed class NullEntryPointResolver : IEntryPointResolver
|
||||
{
|
||||
public Task<IReadOnlyList<EntryPoint>> ResolveAsync(RichGraphV1 graph, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<EntryPoint>>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IVulnSurfaceService for environments without vulnerability surface data.
|
||||
/// </summary>
|
||||
internal sealed class NullVulnSurfaceService : IVulnSurfaceService
|
||||
{
|
||||
public Task<IReadOnlyList<AffectedSymbol>> GetAffectedSymbolsAsync(
|
||||
string vulnId, string componentRef, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<AffectedSymbol>>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IDsseSigningService for environments without signing infrastructure.
|
||||
/// </summary>
|
||||
internal sealed class NullDsseSigningService : StellaOps.Attestor.IDsseSigningService
|
||||
{
|
||||
public Task<byte[]> SignAsync(byte[] payload, string payloadType, string signingKeyId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(Array.Empty<byte>());
|
||||
|
||||
public Task<bool> VerifyAsync(byte[] dsseEnvelope, IReadOnlyList<string> trustedKeyIds, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IArtifactObjectStore for environments without object storage.
|
||||
/// </summary>
|
||||
internal sealed class NullArtifactObjectStore : IArtifactObjectStore
|
||||
{
|
||||
public Task PutAsync(ArtifactObjectDescriptor descriptor, Stream content, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task<Stream?> GetAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<Stream?>(null);
|
||||
|
||||
public Task DeleteAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
@@ -51,10 +51,11 @@ using StellaOps.Scanner.Worker.Processing.Entropy;
|
||||
using StellaOps.Scanner.Worker.Processing.Secrets;
|
||||
using StellaOps.Scanner.Worker.Processing.ServiceSecurity;
|
||||
using StellaOps.Scanner.Worker.Processing.Surface;
|
||||
using StellaOps.Worker.Health;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
var builder = WebApplication.CreateSlimBuilder(args);
|
||||
|
||||
builder.Services.AddOptions<ScannerWorkerOptions>()
|
||||
.BindConfiguration(ScannerWorkerOptions.SectionName)
|
||||
@@ -92,6 +93,7 @@ builder.Services.AddSingleton(new DeterminismContext(
|
||||
workerOptions.Determinism.FilterLogs,
|
||||
workerOptions.Determinism.ConcurrencyLimit));
|
||||
builder.Services.AddSingleton<IDeterministicRandomProvider>(_ => new DeterministicRandomProvider(workerOptions.Determinism.RngSeed));
|
||||
builder.Services.AddSingleton<DeterministicRandomService>();
|
||||
builder.Services.AddScannerCache(builder.Configuration);
|
||||
builder.Services.AddSurfaceEnvironment(options =>
|
||||
{
|
||||
@@ -173,8 +175,21 @@ else
|
||||
{
|
||||
builder.Services.TryAddSingleton<IRubyPackageInventoryStore, NullRubyPackageInventoryStore>();
|
||||
builder.Services.TryAddSingleton<IBunPackageInventoryStore, NullBunPackageInventoryStore>();
|
||||
// Provide fallback registrations for services used by unconditionally-registered components
|
||||
builder.Services.TryAddSingleton(new StellaOps.Scanner.Storage.ScannerStorageOptions());
|
||||
builder.Services.TryAddSingleton<StellaOps.Scanner.Storage.ObjectStore.IArtifactObjectStore, NullArtifactObjectStore>();
|
||||
}
|
||||
|
||||
// Unwrap IOptions<ScannerStorageOptions> to concrete type for classes that take it directly (e.g. ReplayBundleFetcher)
|
||||
builder.Services.TryAddSingleton(sp =>
|
||||
{
|
||||
var opts = sp.GetService<Microsoft.Extensions.Options.IOptions<StellaOps.Scanner.Storage.ScannerStorageOptions>>();
|
||||
return opts?.Value ?? new StellaOps.Scanner.Storage.ScannerStorageOptions();
|
||||
});
|
||||
|
||||
// Ensure IEpssProvider is available even without storage (null fallback)
|
||||
builder.Services.TryAddSingleton<StellaOps.Scanner.Core.Epss.IEpssProvider, NullEpssProvider>();
|
||||
|
||||
builder.Services.TryAddSingleton<IScanJobSource, NullScanJobSource>();
|
||||
builder.Services.TryAddSingleton<IPluginCatalogGuard, RestartOnlyPluginGuard>();
|
||||
builder.Services.AddSingleton<IOSAnalyzerPluginCatalog, OsAnalyzerPluginCatalog>();
|
||||
@@ -260,9 +275,22 @@ if (workerOptions.Secrets.Enabled)
|
||||
builder.Services.AddOptions<StellaOps.Scanner.Core.Configuration.PoEConfiguration>()
|
||||
.BindConfiguration("PoE")
|
||||
.ValidateOnStart();
|
||||
// SubgraphExtractor dependencies (null defaults for environments without richgraph/vulnsurface infra)
|
||||
builder.Services.TryAddSingleton<StellaOps.Scanner.Reachability.IRichGraphStore, NullRichGraphStore>();
|
||||
builder.Services.TryAddSingleton<StellaOps.Scanner.Reachability.IEntryPointResolver, NullEntryPointResolver>();
|
||||
builder.Services.TryAddSingleton<StellaOps.Scanner.Reachability.IVulnSurfaceService, NullVulnSurfaceService>();
|
||||
builder.Services.AddSingleton<StellaOps.Scanner.Reachability.IReachabilityResolver, StellaOps.Scanner.Reachability.SubgraphExtractor>();
|
||||
// PoEArtifactGenerator dependency (null default for environments without signing infra)
|
||||
builder.Services.TryAddSingleton<StellaOps.Attestor.IDsseSigningService, NullDsseSigningService>();
|
||||
// PoEEmissionOptions is a positional record without parameterless ctor - configure explicitly
|
||||
builder.Services.AddSingleton(Microsoft.Extensions.Options.Options.Create(StellaOps.Attestor.PoEEmissionOptions.Default));
|
||||
builder.Services.AddSingleton<StellaOps.Attestor.IProofEmitter, StellaOps.Attestor.PoEArtifactGenerator>();
|
||||
builder.Services.AddSingleton<StellaOps.Signals.Storage.IPoECasStore, StellaOps.Signals.Storage.PoECasStore>();
|
||||
// PoECasStore needs a string casRoot parameter - use factory
|
||||
builder.Services.AddSingleton<StellaOps.Signals.Storage.IPoECasStore>(sp =>
|
||||
new StellaOps.Signals.Storage.PoECasStore(
|
||||
Environment.GetEnvironmentVariable("POE_CAS_ROOT") ?? "/tmp/poe-cas",
|
||||
sp.GetRequiredService<ILogger<StellaOps.Signals.Storage.PoECasStore>>(),
|
||||
sp.GetService<TimeProvider>()));
|
||||
builder.Services.AddSingleton<StellaOps.Scanner.Worker.Orchestration.PoEOrchestrator>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, StellaOps.Scanner.Worker.Processing.PoE.PoEGenerationStageExecutor>();
|
||||
|
||||
@@ -356,21 +384,25 @@ builder.Logging.Configure(options =>
|
||||
| ActivityTrackingOptions.ParentId;
|
||||
});
|
||||
|
||||
var host = builder.Build();
|
||||
builder.Services.AddWorkerHealthChecks();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Fail fast if surface configuration is invalid at startup.
|
||||
using (var scope = host.Services.CreateScope())
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
var env = services.GetRequiredService<ISurfaceEnvironment>();
|
||||
var runner = services.GetRequiredService<ISurfaceValidatorRunner>();
|
||||
await runner.EnsureAsync(
|
||||
SurfaceValidationContext.Create(services, "Scanner.Worker.Startup", env.Settings),
|
||||
host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopping)
|
||||
app.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopping)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await host.RunAsync();
|
||||
app.MapWorkerHealthEndpoints();
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
// Make Program class file-scoped to prevent it from being exposed to referencing assemblies
|
||||
file sealed partial class Program;
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Process" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Worker.Health/StellaOps.Worker.Health.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Determinism.Abstractions/StellaOps.Determinism.Abstractions.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||
|
||||
@@ -477,3 +477,22 @@ public interface IEvidenceStorageService
|
||||
ReachabilityEvidenceJob job,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op call graph snapshot provider for environments without call graph infrastructure.
|
||||
/// </summary>
|
||||
internal sealed class NullCallGraphSnapshotProvider : ICallGraphSnapshotProvider
|
||||
{
|
||||
public Task<CallGraphSnapshot?> GetOrComputeAsync(string imageDigest, bool forceRecompute, CancellationToken ct)
|
||||
=> Task.FromResult<CallGraphSnapshot?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op evidence storage for environments without evidence infrastructure.
|
||||
/// </summary>
|
||||
internal sealed class NullEvidenceStorageService : IEvidenceStorageService
|
||||
{
|
||||
public Task<(string BundleId, string EvidenceUri)> StoreReachabilityStackAsync(
|
||||
ReachabilityStack stack, ReachabilityEvidenceJob job, CancellationToken ct)
|
||||
=> Task.FromResult(("null", "null://no-evidence-store"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Scanner.Explainability.Assumptions;
|
||||
using StellaOps.Scanner.Reachability.Binary;
|
||||
using StellaOps.Scanner.Reachability.Jobs;
|
||||
using StellaOps.Scanner.Reachability.Runtime;
|
||||
@@ -43,14 +44,20 @@ public static class ServiceCollectionExtensions
|
||||
// VEX Integration
|
||||
services.TryAddSingleton<IVexStatusDeterminer, VexStatusDeterminer>();
|
||||
|
||||
// Call graph snapshot provider (required by job executor)
|
||||
services.TryAddSingleton<ICallGraphSnapshotProvider, NullCallGraphSnapshotProvider>();
|
||||
|
||||
// Evidence storage (required by job executor)
|
||||
services.TryAddSingleton<IEvidenceStorageService, NullEvidenceStorageService>();
|
||||
|
||||
// Job Executor
|
||||
services.TryAddScoped<IReachabilityEvidenceJobExecutor, ReachabilityEvidenceJobExecutor>();
|
||||
|
||||
// Runtime Collection (optional - requires eBPF infrastructure)
|
||||
services.TryAddSingleton<IRuntimeReachabilityCollector, EbpfRuntimeReachabilityCollector>();
|
||||
// Runtime Collection (optional - requires eBPF infrastructure; null default for environments without eBPF)
|
||||
services.TryAddSingleton<IRuntimeReachabilityCollector, NullRuntimeReachabilityCollector>();
|
||||
|
||||
// Binary Patch Verification (requires Ghidra infrastructure)
|
||||
services.TryAddSingleton<IBinaryPatchVerifier, BinaryPatchVerifier>();
|
||||
// Binary Patch Verification (requires Ghidra infrastructure; null default for environments without Ghidra)
|
||||
services.TryAddSingleton<IBinaryPatchVerifier, NullBinaryPatchVerifier>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -71,9 +78,11 @@ public static class ServiceCollectionExtensions
|
||||
services.TryAddSingleton(cveSymbolMappingFactory);
|
||||
services.TryAddSingleton<IReachabilityStackEvaluator, ReachabilityStackEvaluator>();
|
||||
services.TryAddSingleton<IVexStatusDeterminer, VexStatusDeterminer>();
|
||||
services.TryAddSingleton<ICallGraphSnapshotProvider, NullCallGraphSnapshotProvider>();
|
||||
services.TryAddSingleton<IEvidenceStorageService, NullEvidenceStorageService>();
|
||||
services.TryAddScoped<IReachabilityEvidenceJobExecutor, ReachabilityEvidenceJobExecutor>();
|
||||
services.TryAddSingleton<IRuntimeReachabilityCollector, EbpfRuntimeReachabilityCollector>();
|
||||
services.TryAddSingleton<IBinaryPatchVerifier, BinaryPatchVerifier>();
|
||||
services.TryAddSingleton<IRuntimeReachabilityCollector, NullRuntimeReachabilityCollector>();
|
||||
services.TryAddSingleton<IBinaryPatchVerifier, NullBinaryPatchVerifier>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -140,3 +149,65 @@ public sealed class ReachabilityEvidenceOptions
|
||||
/// </summary>
|
||||
public int MaxJobTimeoutSeconds { get; set; } = 300;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IBinaryPatchVerifier for environments without Ghidra.
|
||||
/// </summary>
|
||||
internal sealed class NullBinaryPatchVerifier : IBinaryPatchVerifier
|
||||
{
|
||||
public Task<PatchVerificationResult> VerifyPatchAsync(PatchVerificationRequest request, CancellationToken ct = default)
|
||||
=> Task.FromResult(new PatchVerificationResult
|
||||
{
|
||||
Success = false,
|
||||
Status = PatchStatus.Unknown,
|
||||
FunctionResults = [],
|
||||
Layer2 = new ReachabilityLayer2
|
||||
{
|
||||
IsResolved = false,
|
||||
Confidence = ConfidenceLevel.Low,
|
||||
Reason = "Binary patch verification not available (Ghidra not configured)"
|
||||
},
|
||||
Confidence = 0m,
|
||||
Error = "Binary patch verification not available (Ghidra not configured)"
|
||||
});
|
||||
|
||||
public Task<FunctionPatchResult> CompareFunctionAsync(string vulnerableBinaryPath, string targetBinaryPath, string symbolName, CancellationToken ct = default)
|
||||
=> Task.FromResult(new FunctionPatchResult
|
||||
{
|
||||
SymbolName = symbolName,
|
||||
Success = false,
|
||||
IsPatched = false,
|
||||
Similarity = 0m,
|
||||
Confidence = 0m,
|
||||
Error = "Binary patch verification not available (Ghidra not configured)"
|
||||
});
|
||||
|
||||
public bool IsSupported(string binaryPath) => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null implementation of IRuntimeReachabilityCollector for environments without eBPF.
|
||||
/// </summary>
|
||||
internal sealed class NullRuntimeReachabilityCollector : IRuntimeReachabilityCollector
|
||||
{
|
||||
public bool IsAvailable => false;
|
||||
public string Platform => "none";
|
||||
|
||||
public Task<RuntimeReachabilityResult> ObserveAsync(RuntimeObservationRequest request, CancellationToken ct = default)
|
||||
=> Task.FromResult(new RuntimeReachabilityResult
|
||||
{
|
||||
Success = false,
|
||||
Layer3 = new ReachabilityLayer3
|
||||
{
|
||||
IsGated = false,
|
||||
Outcome = GatingOutcome.NotGated,
|
||||
Confidence = ConfidenceLevel.Low,
|
||||
Description = "Runtime observation not available (eBPF not configured)"
|
||||
},
|
||||
Source = ObservationSource.None,
|
||||
Error = "Runtime observation not available (eBPF not configured)"
|
||||
});
|
||||
|
||||
public Task<IReadOnlyList<SymbolObservation>> CheckObservationsAsync(string containerId, IReadOnlyList<string> symbols, CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<SymbolObservation>>([]);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class NullEpssSignalPublisher : IEpssSignalPublisher
|
||||
{
|
||||
public static readonly NullEpssSignalPublisher Instance = new();
|
||||
|
||||
private NullEpssSignalPublisher() { }
|
||||
public NullEpssSignalPublisher() { }
|
||||
|
||||
public Task<EpssSignalPublishResult> PublishAsync(EpssSignal signal, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new EpssSignalPublishResult { Success = true, MessageId = "null" });
|
||||
|
||||
@@ -95,7 +95,13 @@ public static class ServiceCollectionExtensions
|
||||
services.AddSingleton<EpssCsvStreamParser>();
|
||||
services.AddScoped<IEpssRepository, PostgresEpssRepository>();
|
||||
services.AddSingleton<EpssOnlineSource>();
|
||||
services.AddSingleton<EpssBundleSource>();
|
||||
services.AddSingleton<EpssBundleSource>(sp =>
|
||||
{
|
||||
var opts = sp.GetRequiredService<IOptions<ScannerStorageOptions>>().Value;
|
||||
// Default to /app/epss for bundle path; overrideable via config.
|
||||
var path = Environment.GetEnvironmentVariable("EPSS_BUNDLE_PATH") ?? "/app/epss";
|
||||
return new EpssBundleSource(path);
|
||||
});
|
||||
// Note: EpssChangeDetector is a static class, no DI registration needed
|
||||
|
||||
// EPSS provider with optional Valkey cache layer (Sprint: SPRINT_3410_0002_0001, Task: EPSS-SCAN-005)
|
||||
|
||||
Reference in New Issue
Block a user