save audit remarks applications progress

This commit is contained in:
StellaOps Bot
2026-01-04 22:49:53 +02:00
parent 8862e112c4
commit eca4e964d3
48 changed files with 1850 additions and 112 deletions

View File

@@ -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()
});
}
}

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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)
});
}

View File

@@ -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];

View File

@@ -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;

View File

@@ -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);

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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 };
}