save audit remarks applications progress
This commit is contained in:
@@ -11,6 +11,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ElfHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// ELF magic bytes
|
||||
private static readonly byte[] ElfMagic = [0x7F, 0x45, 0x4C, 0x46]; // \x7FELF
|
||||
|
||||
@@ -596,7 +603,7 @@ public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
|
||||
#endregion
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -623,7 +630,7 @@ public sealed class ElfHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
private static ushort ReadUInt16(ReadOnlySpan<byte> span, bool littleEndian)
|
||||
|
||||
@@ -17,6 +17,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public MachoHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// Mach-O magic numbers
|
||||
private const uint MH_MAGIC = 0xFEEDFACE; // 32-bit
|
||||
private const uint MH_CIGAM = 0xCEFAEDFE; // 32-bit (reversed)
|
||||
@@ -257,7 +264,7 @@ public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
: BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(offset, 4));
|
||||
}
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -283,6 +290,6 @@ public sealed class MachoHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace StellaOps.Scanner.Analyzers.Native.Hardening;
|
||||
/// </summary>
|
||||
public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PeHardeningExtractor(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// PE magic bytes: MZ (DOS header)
|
||||
private const ushort DOS_MAGIC = 0x5A4D; // "MZ"
|
||||
private const uint PE_SIGNATURE = 0x00004550; // "PE\0\0"
|
||||
@@ -233,7 +240,7 @@ public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryHardeningFlags CreateResult(
|
||||
private BinaryHardeningFlags CreateResult(
|
||||
string path,
|
||||
string digest,
|
||||
List<HardeningFlag> flags,
|
||||
@@ -259,6 +266,6 @@ public sealed class PeHardeningExtractor : IHardeningExtractor
|
||||
Flags: [.. flags],
|
||||
HardeningScore: Math.Round(score, 2),
|
||||
MissingFlags: [.. missing],
|
||||
ExtractedAt: DateTimeOffset.UtcNow);
|
||||
ExtractedAt: _timeProvider.GetUtcNow());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
private readonly BuildIdIndexOptions _options;
|
||||
private readonly ILogger<OfflineBuildIdIndex> _logger;
|
||||
private readonly IDsseSigningService? _dsseSigningService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private FrozenDictionary<string, BuildIdLookupResult> _index = FrozenDictionary<string, BuildIdLookupResult>.Empty;
|
||||
private bool _isLoaded;
|
||||
|
||||
@@ -31,7 +32,8 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
public OfflineBuildIdIndex(
|
||||
IOptions<BuildIdIndexOptions> options,
|
||||
ILogger<OfflineBuildIdIndex> logger,
|
||||
IDsseSigningService? dsseSigningService = null)
|
||||
IDsseSigningService? dsseSigningService = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
@@ -39,6 +41,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_dsseSigningService = dsseSigningService;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -176,7 +179,7 @@ public sealed class OfflineBuildIdIndex : IBuildIdIndex
|
||||
// Check index freshness
|
||||
if (_options.MaxIndexAge > TimeSpan.Zero)
|
||||
{
|
||||
var oldestAllowed = DateTimeOffset.UtcNow - _options.MaxIndexAge;
|
||||
var oldestAllowed = _timeProvider.GetUtcNow() - _options.MaxIndexAge;
|
||||
var latestEntry = entries.Values.MaxBy(e => e.IndexedAt);
|
||||
if (latestEntry is not null && latestEntry.IndexedAt < oldestAllowed)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -22,6 +23,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("linux")]
|
||||
public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -33,6 +36,17 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Linux eBPF capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public LinuxEbpfCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "linux-ebpf-dlopen";
|
||||
|
||||
@@ -152,8 +166,8 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -243,7 +257,7 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -405,7 +419,7 @@ public sealed class LinuxEbpfCaptureAdapter : IRuntimeCaptureAdapter
|
||||
if (parts[0] == "DLOPEN" && parts.Length >= 5)
|
||||
{
|
||||
return new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: int.Parse(parts[1]),
|
||||
ThreadId: int.Parse(parts[2]),
|
||||
LoadType: RuntimeLoadType.Dlopen,
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -23,6 +24,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("macos")]
|
||||
public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -34,6 +37,17 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new macOS dyld capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public MacOsDyldCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "macos-dyld-interpose";
|
||||
|
||||
@@ -156,8 +170,8 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -247,7 +261,7 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -417,7 +431,7 @@ public sealed class MacOsDyldCaptureAdapter : IRuntimeCaptureAdapter
|
||||
: RuntimeLoadType.MacOsDlopen;
|
||||
|
||||
return new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: int.Parse(parts[1]),
|
||||
ThreadId: int.Parse(parts[2]),
|
||||
LoadType: loadType,
|
||||
|
||||
@@ -48,11 +48,13 @@ public static class RuntimeEvidenceAggregator
|
||||
/// <param name="runtimeEvidence">Runtime capture evidence.</param>
|
||||
/// <param name="staticEdges">Static analysis dependency edges.</param>
|
||||
/// <param name="heuristicEdges">Heuristic analysis edges.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <returns>Merged evidence document.</returns>
|
||||
public static MergedEvidence MergeWithStaticAnalysis(
|
||||
RuntimeEvidence runtimeEvidence,
|
||||
IEnumerable<Observations.NativeObservationDeclaredEdge> staticEdges,
|
||||
IEnumerable<Observations.NativeObservationHeuristicEdge> heuristicEdges)
|
||||
IEnumerable<Observations.NativeObservationHeuristicEdge> heuristicEdges,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
var staticList = staticEdges.ToList();
|
||||
var heuristicList = heuristicEdges.ToList();
|
||||
@@ -140,6 +142,7 @@ public static class RuntimeEvidenceAggregator
|
||||
}
|
||||
}
|
||||
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
return new MergedEvidence(
|
||||
ConfirmedEdges: confirmedEdges,
|
||||
StaticOnlyEdges: staticOnlyEdges,
|
||||
@@ -148,7 +151,7 @@ public static class RuntimeEvidenceAggregator
|
||||
TotalRuntimeEvents: runtimeEvidence.Sessions.Sum(s => s.Events.Count),
|
||||
TotalDroppedEvents: runtimeEvidence.Sessions.Sum(s => s.TotalEventsDropped),
|
||||
CaptureStartTime: runtimeEvidence.Sessions.Min(s => s.StartTime),
|
||||
CaptureEndTime: runtimeEvidence.Sessions.Max(s => s.EndTime ?? DateTime.UtcNow));
|
||||
CaptureEndTime: runtimeEvidence.Sessions.Max(s => s.EndTime ?? tp.GetUtcNow().UtcDateTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -273,7 +273,9 @@ public sealed record CollapsedStack
|
||||
/// Parses a collapsed stack line.
|
||||
/// Format: "container@digest;buildid=xxx;func;... count"
|
||||
/// </summary>
|
||||
public static CollapsedStack? Parse(string line)
|
||||
/// <param name="line">The collapsed stack line to parse.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
public static CollapsedStack? Parse(string line, TimeProvider? timeProvider = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return null;
|
||||
@@ -305,7 +307,8 @@ public sealed record CollapsedStack
|
||||
}
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
var now = tp.GetUtcNow().UtcDateTime;
|
||||
return new CollapsedStack
|
||||
{
|
||||
ContainerIdentifier = container,
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Principal;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
|
||||
@@ -21,6 +22,8 @@ namespace StellaOps.Scanner.Analyzers.Native.RuntimeCapture;
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
private readonly ConcurrentBag<RuntimeLoadEvent> _events = [];
|
||||
private readonly object _stateLock = new();
|
||||
private CaptureState _state = CaptureState.Idle;
|
||||
@@ -34,6 +37,17 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
private long _droppedEvents;
|
||||
private int _redactedPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Windows ETW capture adapter.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <param name="guidProvider">Optional GUID provider for deterministic session IDs.</param>
|
||||
public WindowsEtwCaptureAdapter(TimeProvider? timeProvider = null, IGuidProvider? guidProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AdapterId => "windows-etw-imageload";
|
||||
|
||||
@@ -146,8 +160,8 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
_events.Clear();
|
||||
_droppedEvents = 0;
|
||||
_redactedPaths = 0;
|
||||
SessionId = Guid.NewGuid().ToString("N");
|
||||
_startTime = DateTime.UtcNow;
|
||||
SessionId = _guidProvider.NewGuid().ToString("N");
|
||||
_startTime = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
_captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
try
|
||||
@@ -240,7 +254,7 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
var session = new RuntimeCaptureSession(
|
||||
SessionId: SessionId ?? "unknown",
|
||||
StartTime: _startTime,
|
||||
EndTime: DateTime.UtcNow,
|
||||
EndTime: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
Platform: Platform,
|
||||
CaptureMethod: CaptureMethod,
|
||||
TargetProcessId: _options?.TargetProcessId,
|
||||
@@ -480,7 +494,7 @@ public sealed class WindowsEtwCaptureAdapter : IRuntimeCaptureAdapter
|
||||
: RuntimeLoadType.LoadLibrary;
|
||||
|
||||
var evt = new RuntimeLoadEvent(
|
||||
Timestamp: DateTime.UtcNow,
|
||||
Timestamp: _timeProvider.GetUtcNow().UtcDateTime,
|
||||
ProcessId: processId,
|
||||
ThreadId: 0,
|
||||
LoadType: loadType,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\__Libraries\\StellaOps.Scanner.ProofSpine\\StellaOps.Scanner.ProofSpine.csproj" />
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Determinism.Abstractions\\StellaOps.Determinism.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -151,6 +151,7 @@ public static class EpssEndpoints
|
||||
private static async Task<IResult> GetHistory(
|
||||
[FromRoute] string cveId,
|
||||
[FromServices] IEpssProvider epssProvider,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
[FromQuery] string? startDate = null,
|
||||
[FromQuery] string? endDate = null,
|
||||
[FromQuery] int days = 30,
|
||||
@@ -183,7 +184,7 @@ public static class EpssEndpoints
|
||||
else
|
||||
{
|
||||
// Default to last N days
|
||||
end = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
end = DateOnly.FromDateTime(timeProvider.GetUtcNow().UtcDateTime);
|
||||
start = end.AddDays(-days);
|
||||
}
|
||||
|
||||
@@ -213,6 +214,7 @@ public static class EpssEndpoints
|
||||
/// </summary>
|
||||
private static async Task<IResult> GetStatus(
|
||||
[FromServices] IEpssProvider epssProvider,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var isAvailable = await epssProvider.IsAvailableAsync(cancellationToken);
|
||||
@@ -222,7 +224,7 @@ public static class EpssEndpoints
|
||||
{
|
||||
Available = isAvailable,
|
||||
LatestModelDate = modelDate?.ToString("yyyy-MM-dd"),
|
||||
LastCheckedUtc = DateTimeOffset.UtcNow
|
||||
LastCheckedUtc = timeProvider.GetUtcNow()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ internal static class EvidenceEndpoints
|
||||
string scanId,
|
||||
string findingId,
|
||||
IEvidenceCompositionService evidenceService,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -108,7 +109,7 @@ internal static class EvidenceEndpoints
|
||||
}
|
||||
else if (evidence.Freshness.ExpiresAt.HasValue)
|
||||
{
|
||||
var timeUntilExpiry = evidence.Freshness.ExpiresAt.Value - DateTimeOffset.UtcNow;
|
||||
var timeUntilExpiry = evidence.Freshness.ExpiresAt.Value - timeProvider.GetUtcNow();
|
||||
if (timeUntilExpiry <= TimeSpan.FromDays(1))
|
||||
{
|
||||
context.Response.Headers["X-Evidence-Warning"] = "near-expiry";
|
||||
|
||||
@@ -270,6 +270,7 @@ internal static class SmartDiffEndpoints
|
||||
string candidateId,
|
||||
ReviewRequest request,
|
||||
IVexCandidateStore store,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext httpContext,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
@@ -282,7 +283,7 @@ internal static class SmartDiffEndpoints
|
||||
var review = new VexCandidateReview(
|
||||
Action: action,
|
||||
Reviewer: reviewer,
|
||||
ReviewedAt: DateTimeOffset.UtcNow,
|
||||
ReviewedAt: timeProvider.GetUtcNow(),
|
||||
Comment: request.Comment);
|
||||
|
||||
var success = await store.ReviewCandidateAsync(candidateId, review, ct);
|
||||
|
||||
@@ -41,6 +41,7 @@ internal static class ProofBundleEndpoints
|
||||
private static async Task<IResult> HandleGenerateProofBundleAsync(
|
||||
[FromBody] ProofBundleRequest request,
|
||||
[FromServices] IProofBundleGenerator bundleGenerator,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bundleGenerator);
|
||||
@@ -67,7 +68,7 @@ internal static class ProofBundleEndpoints
|
||||
{
|
||||
PathId = request.PathId,
|
||||
Bundle = bundle,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
return Results.Ok(response);
|
||||
|
||||
@@ -50,6 +50,7 @@ internal static class TriageInboxEndpoints
|
||||
[FromQuery] string? filter,
|
||||
[FromServices] IExploitPathGroupingService groupingService,
|
||||
[FromServices] IFindingQueryService findingService,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(groupingService);
|
||||
@@ -77,7 +78,7 @@ internal static class TriageInboxEndpoints
|
||||
FilteredPaths = filteredPaths.Count,
|
||||
Filter = filter,
|
||||
Paths = filteredPaths,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
return Results.Ok(response);
|
||||
|
||||
@@ -55,6 +55,7 @@ internal static class UnknownsEndpoints
|
||||
[FromQuery] int? limit,
|
||||
IUnknownRepository repository,
|
||||
IUnknownRanker ranker,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Validate and default pagination
|
||||
@@ -95,9 +96,10 @@ internal static class UnknownsEndpoints
|
||||
PageSize: pageSize);
|
||||
|
||||
var result = await repository.ListUnknownsAsync(query, cancellationToken);
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
return Results.Ok(new UnknownsListResponse(
|
||||
Items: result.Items.Select(UnknownItemResponse.FromUnknownItem).ToList(),
|
||||
Items: result.Items.Select(item => UnknownItemResponse.FromUnknownItem(item, now)).ToList(),
|
||||
TotalCount: result.TotalCount,
|
||||
Page: pageNum,
|
||||
PageSize: pageSize,
|
||||
@@ -195,7 +197,7 @@ public sealed record UnknownItemResponse(
|
||||
ContainmentResponse? Containment,
|
||||
DateTimeOffset CreatedAt)
|
||||
{
|
||||
public static UnknownItemResponse FromUnknownItem(UnknownItem item) => new(
|
||||
public static UnknownItemResponse FromUnknownItem(UnknownItem item, DateTimeOffset now) => new(
|
||||
Id: Guid.TryParse(item.Id, out var id) ? id : Guid.Empty,
|
||||
SubjectRef: item.ArtifactPurl ?? item.ArtifactDigest,
|
||||
Kind: string.Join(",", item.Reasons),
|
||||
@@ -209,7 +211,7 @@ public sealed record UnknownItemResponse(
|
||||
Containment: item.Containment != null
|
||||
? new ContainmentResponse(item.Containment.Seccomp, item.Containment.Fs)
|
||||
: null,
|
||||
CreatedAt: DateTimeOffset.UtcNow); // Would come from Unknown.SysFrom
|
||||
CreatedAt: now); // Would come from Unknown.SysFrom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -120,6 +120,7 @@ internal static class WitnessEndpoints
|
||||
private static async Task<IResult> HandleVerifyWitnessAsync(
|
||||
Guid witnessId,
|
||||
IWitnessRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(repository);
|
||||
@@ -161,10 +162,11 @@ internal static class WitnessEndpoints
|
||||
}
|
||||
|
||||
// Record verification attempt
|
||||
var now = timeProvider.GetUtcNow();
|
||||
await repository.RecordVerificationAsync(new WitnessVerificationRecord
|
||||
{
|
||||
WitnessId = witnessId,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = now,
|
||||
VerifiedBy = "api",
|
||||
VerificationStatus = verificationStatus,
|
||||
VerificationError = verificationError
|
||||
@@ -176,7 +178,7 @@ internal static class WitnessEndpoints
|
||||
WitnessHash = witness.WitnessHash,
|
||||
Status = verificationStatus,
|
||||
Error = verificationError,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = now,
|
||||
IsSigned = !string.IsNullOrEmpty(witness.DsseEnvelope)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static async Task PrepareEvidenceFilesAsync(
|
||||
private async Task PrepareEvidenceFilesAsync(
|
||||
UnifiedEvidenceResponseDto evidence,
|
||||
List<(string path, MemoryStream stream, string contentType)> streams,
|
||||
List<ArchiveFileEntry> entries,
|
||||
@@ -621,7 +621,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CreateTarGzArchiveAsync(
|
||||
private async Task CreateTarGzArchiveAsync(
|
||||
string findingId,
|
||||
List<(string path, MemoryStream stream, string contentType)> files,
|
||||
Stream outputStream,
|
||||
@@ -660,7 +660,7 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
await gzipStream.WriteAsync(endBlocks, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static byte[] CreateTarHeader(string name, long size)
|
||||
private byte[] CreateTarHeader(string name, long size)
|
||||
{
|
||||
var header = new byte[512];
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ public sealed class GatingReasonService : IGatingReasonService
|
||||
/// <summary>
|
||||
/// Computes a composite trust score for a VEX record.
|
||||
/// </summary>
|
||||
private static double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
private double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
{
|
||||
// Weighted combination of trust factors
|
||||
const double IssuerWeight = 0.4;
|
||||
|
||||
@@ -29,6 +29,7 @@ public sealed class ReportSigner : IReportSigner
|
||||
private readonly ILogger<ReportSigner> logger;
|
||||
private readonly ICryptoProviderRegistry cryptoRegistry;
|
||||
private readonly ICryptoHmac cryptoHmac;
|
||||
private readonly TimeProvider timeProvider;
|
||||
private readonly ICryptoProvider? provider;
|
||||
private readonly CryptoKeyReference? keyReference;
|
||||
private readonly CryptoSignerResolution? signerResolution;
|
||||
@@ -38,11 +39,13 @@ public sealed class ReportSigner : IReportSigner
|
||||
IOptions<ScannerWebServiceOptions> options,
|
||||
ICryptoProviderRegistry cryptoRegistry,
|
||||
ICryptoHmac cryptoHmac,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<ReportSigner> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
this.cryptoRegistry = cryptoRegistry ?? throw new ArgumentNullException(nameof(cryptoRegistry));
|
||||
this.cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
|
||||
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var value = options.Value ?? new ScannerWebServiceOptions();
|
||||
@@ -79,7 +82,7 @@ public sealed class ReportSigner : IReportSigner
|
||||
reference,
|
||||
canonicalAlgorithm,
|
||||
privateKey,
|
||||
createdAt: DateTimeOffset.UtcNow);
|
||||
createdAt: timeProvider.GetUtcNow());
|
||||
|
||||
provider.UpsertSigningKey(signingKeyDescriptor);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
private readonly IProofBundleWriter _bundleWriter;
|
||||
private readonly IScanManifestSigner _manifestSigner;
|
||||
private readonly IScoringService _scoringService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<ScoreReplayService> _logger;
|
||||
|
||||
public ScoreReplayService(
|
||||
@@ -31,6 +32,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
IProofBundleWriter bundleWriter,
|
||||
IScanManifestSigner manifestSigner,
|
||||
IScoringService scoringService,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<ScoreReplayService> logger)
|
||||
{
|
||||
_manifestRepository = manifestRepository ?? throw new ArgumentNullException(nameof(manifestRepository));
|
||||
@@ -38,6 +40,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
_bundleWriter = bundleWriter ?? throw new ArgumentNullException(nameof(bundleWriter));
|
||||
_manifestSigner = manifestSigner ?? throw new ArgumentNullException(nameof(manifestSigner));
|
||||
_scoringService = scoringService ?? throw new ArgumentNullException(nameof(scoringService));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -99,7 +102,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
RootHash: bundle.RootHash,
|
||||
BundleUri: bundle.BundleUri,
|
||||
ManifestHash: manifest.ComputeHash(),
|
||||
ReplayedAt: DateTimeOffset.UtcNow,
|
||||
ReplayedAt: _timeProvider.GetUtcNow(),
|
||||
Deterministic: manifest.Deterministic);
|
||||
}
|
||||
finally
|
||||
@@ -164,7 +167,7 @@ public sealed class ScoreReplayService : IScoreReplayService
|
||||
ComputedRootHash: computedRootHash,
|
||||
ManifestValid: manifestVerify.IsValid,
|
||||
LedgerValid: ledgerValid,
|
||||
VerifiedAt: DateTimeOffset.UtcNow,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
ErrorMessage: string.Join("; ", errors));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
private readonly SliceHasher _hasher;
|
||||
private readonly IFileContentAddressableStore _cas;
|
||||
private readonly IScanMetadataRepository _scanRepo;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly SliceQueryServiceOptions _options;
|
||||
private readonly ILogger<SliceQueryService> _logger;
|
||||
|
||||
@@ -51,6 +52,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
SliceHasher hasher,
|
||||
IFileContentAddressableStore cas,
|
||||
IScanMetadataRepository scanRepo,
|
||||
TimeProvider timeProvider,
|
||||
IOptions<SliceQueryServiceOptions> options,
|
||||
ILogger<SliceQueryService> logger)
|
||||
{
|
||||
@@ -61,6 +63,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
_hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
|
||||
_cas = cas ?? throw new ArgumentNullException(nameof(cas));
|
||||
_scanRepo = scanRepo ?? throw new ArgumentNullException(nameof(scanRepo));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_options = options?.Value ?? new SliceQueryServiceOptions();
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
@@ -121,7 +124,7 @@ public sealed class SliceQueryService : ISliceQueryService
|
||||
PathWitnesses = slice.Verdict.PathWitnesses.IsDefaultOrEmpty
|
||||
? Array.Empty<string>()
|
||||
: slice.Verdict.PathWitnesses.ToList(),
|
||||
CachedAt = DateTimeOffset.UtcNow
|
||||
CachedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
await _cache.SetAsync(cacheKey, cacheEntry, TimeSpan.FromHours(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Reposi
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ProofBundleRow> _bundlesByRootHash = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<Guid, List<ProofBundleRow>> _bundlesByScanId = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public TestProofBundleRepository(TimeProvider timeProvider)
|
||||
{
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
}
|
||||
|
||||
public Task<ProofBundleRow?> GetByRootHashAsync(string rootHash, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -112,8 +118,8 @@ public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Reposi
|
||||
public Task<int> DeleteExpiredAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var expired = _bundlesByRootHash.Values
|
||||
.Where(b => b.ExpiresAt.HasValue && b.ExpiresAt.Value < now)
|
||||
.ToList();
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
private readonly TriageDbContext _dbContext;
|
||||
private readonly IGatingReasonService _gatingService;
|
||||
private readonly IReplayCommandService _replayService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<UnifiedEvidenceService> _logger;
|
||||
|
||||
private const double DefaultPolicyTrustThreshold = 0.7;
|
||||
@@ -30,11 +31,13 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
TriageDbContext dbContext,
|
||||
IGatingReasonService gatingService,
|
||||
IReplayCommandService replayService,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<UnifiedEvidenceService> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_gatingService = gatingService ?? throw new ArgumentNullException(nameof(gatingService));
|
||||
_replayService = replayService ?? throw new ArgumentNullException(nameof(replayService));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -106,7 +109,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
ReplayCommand = replayResponse?.FullCommand?.Command,
|
||||
ShortReplayCommand = replayResponse?.ShortCommand?.Command,
|
||||
EvidenceBundleUrl = replayResponse?.Bundle?.DownloadUri,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
GeneratedAt = _timeProvider.GetUtcNow(),
|
||||
CacheKey = cacheKey
|
||||
};
|
||||
}
|
||||
@@ -277,11 +280,11 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
AttestationsVerified = hasAttestations,
|
||||
EvidenceComplete = hasVex && hasReachability,
|
||||
Issues = issues.Count > 0 ? issues : null,
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
private static double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
private double ComputeVexTrustScore(TriageEffectiveVex vex)
|
||||
{
|
||||
const double IssuerWeight = 0.4;
|
||||
const double RecencyWeight = 0.2;
|
||||
@@ -289,7 +292,7 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
const double EvidenceWeight = 0.2;
|
||||
|
||||
var issuerTrust = GetIssuerTrust(vex.Issuer);
|
||||
var recencyTrust = GetRecencyTrust((DateTimeOffset?)vex.ValidFrom);
|
||||
var recencyTrust = GetRecencyTrust((DateTimeOffset?)vex.ValidFrom, _timeProvider.GetUtcNow());
|
||||
var justificationTrust = GetJustificationTrust(vex.PrunedSourcesJson);
|
||||
var evidenceTrust = !string.IsNullOrEmpty(vex.DsseEnvelopeHash) ? 0.8 : 0.3;
|
||||
|
||||
@@ -309,10 +312,10 @@ public sealed class UnifiedEvidenceService : IUnifiedEvidenceService
|
||||
_ => 0.5
|
||||
};
|
||||
|
||||
private static double GetRecencyTrust(DateTimeOffset? timestamp)
|
||||
private static double GetRecencyTrust(DateTimeOffset? timestamp, DateTimeOffset now)
|
||||
{
|
||||
if (timestamp is null) return 0.3;
|
||||
var age = DateTimeOffset.UtcNow - timestamp.Value;
|
||||
var age = now - timestamp.Value;
|
||||
return age.TotalDays switch { <= 7 => 1.0, <= 30 => 0.9, <= 90 => 0.7, <= 365 => 0.5, _ => 0.3 };
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ public sealed record SourceRunResponse
|
||||
Status = run.Status,
|
||||
StartedAt = run.StartedAt,
|
||||
CompletedAt = run.CompletedAt,
|
||||
DurationMs = run.DurationMs,
|
||||
DurationMs = run.GetDurationMs(),
|
||||
ItemsDiscovered = run.ItemsDiscovered,
|
||||
ItemsScanned = run.ItemsScanned,
|
||||
ItemsSucceeded = run.ItemsSucceeded,
|
||||
|
||||
@@ -84,8 +84,9 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
source.TenantId,
|
||||
context.Trigger,
|
||||
context.CorrelationId,
|
||||
_timeProvider,
|
||||
context.TriggerDetails);
|
||||
failedRun.Fail(canTrigger.Error!);
|
||||
failedRun.Fail(canTrigger.Error!, _timeProvider);
|
||||
await _runRepository.CreateAsync(failedRun, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -102,6 +103,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
source.TenantId,
|
||||
context.Trigger,
|
||||
context.CorrelationId,
|
||||
_timeProvider,
|
||||
context.TriggerDetails);
|
||||
|
||||
await _runRepository.CreateAsync(run, ct);
|
||||
@@ -112,7 +114,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
var handler = GetHandler(source.SourceType);
|
||||
if (handler == null)
|
||||
{
|
||||
run.Fail($"No handler registered for source type {source.SourceType}");
|
||||
run.Fail($"No handler registered for source type {source.SourceType}", _timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
return new TriggerDispatchResult
|
||||
{
|
||||
@@ -133,9 +135,9 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
run.Complete();
|
||||
run.Complete(_timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow(), _timeProvider);
|
||||
await _sourceRepository.UpdateAsync(source, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -176,13 +178,13 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
// 7. Complete or fail based on results
|
||||
if (run.ItemsFailed == run.ItemsDiscovered)
|
||||
{
|
||||
run.Fail("All targets failed to queue");
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), run.ErrorMessage!);
|
||||
run.Fail("All targets failed to queue", _timeProvider);
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), run.ErrorMessage!, _timeProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
run.Complete();
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
|
||||
run.Complete(_timeProvider);
|
||||
source.RecordSuccessfulRun(_timeProvider.GetUtcNow(), _timeProvider);
|
||||
}
|
||||
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
@@ -199,10 +201,10 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
{
|
||||
_logger.LogError(ex, "Dispatch failed for source {SourceId}", sourceId);
|
||||
|
||||
run.Fail(ex.Message);
|
||||
run.Fail(ex.Message, _timeProvider);
|
||||
await _runRepository.UpdateAsync(run, ct);
|
||||
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), ex.Message);
|
||||
source.RecordFailedRun(_timeProvider.GetUtcNow(), ex.Message, _timeProvider);
|
||||
await _sourceRepository.UpdateAsync(source, ct);
|
||||
|
||||
return new TriggerDispatchResult
|
||||
@@ -266,7 +268,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
return _handlers.FirstOrDefault(h => h.SourceType == sourceType);
|
||||
}
|
||||
|
||||
private static (bool Success, string? Error) CanTrigger(SbomSource source, TriggerContext context)
|
||||
private (bool Success, string? Error) CanTrigger(SbomSource source, TriggerContext context)
|
||||
{
|
||||
if (source.Status == SbomSourceStatus.Disabled)
|
||||
{
|
||||
@@ -292,7 +294,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
if (source.IsRateLimited())
|
||||
if (source.IsRateLimited(_timeProvider))
|
||||
{
|
||||
return (false, "Source is rate limited");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user