finish secrets finding work and audit remarks work save
This commit is contained in:
@@ -18,7 +18,17 @@ public sealed record BoundaryExtractionContext
|
||||
/// <summary>
|
||||
/// Empty context for simple extractions.
|
||||
/// </summary>
|
||||
public static readonly BoundaryExtractionContext Empty = new();
|
||||
/// <remarks>Uses system time. For deterministic timestamps, use <see cref="CreateEmpty"/>.</remarks>
|
||||
[Obsolete("Use CreateEmpty(TimeProvider) for deterministic timestamps")]
|
||||
public static BoundaryExtractionContext Empty => CreateEmpty();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty context for simple extractions.
|
||||
/// </summary>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
/// <returns>An empty boundary extraction context.</returns>
|
||||
public static BoundaryExtractionContext CreateEmpty(TimeProvider? timeProvider = null) =>
|
||||
new() { Timestamp = (timeProvider ?? TimeProvider.System).GetUtcNow() };
|
||||
|
||||
/// <summary>
|
||||
/// Environment identifier (e.g., "production", "staging").
|
||||
@@ -61,7 +71,7 @@ public sealed record BoundaryExtractionContext
|
||||
/// <summary>
|
||||
/// Timestamp for the context (for cache invalidation).
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Source of this context (e.g., "k8s", "iac", "runtime").
|
||||
@@ -71,20 +81,28 @@ public sealed record BoundaryExtractionContext
|
||||
/// <summary>
|
||||
/// Creates a context from detected gates.
|
||||
/// </summary>
|
||||
public static BoundaryExtractionContext FromGates(IReadOnlyList<DetectedGate> gates) =>
|
||||
new() { DetectedGates = gates };
|
||||
/// <param name="gates">The detected gates.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
public static BoundaryExtractionContext FromGates(IReadOnlyList<DetectedGate> gates, TimeProvider? timeProvider = null) =>
|
||||
new() { DetectedGates = gates, Timestamp = (timeProvider ?? TimeProvider.System).GetUtcNow() };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a context with environment hints.
|
||||
/// </summary>
|
||||
/// <param name="environmentId">The environment identifier.</param>
|
||||
/// <param name="isInternetFacing">Whether the service is internet-facing.</param>
|
||||
/// <param name="networkZone">The network zone.</param>
|
||||
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
|
||||
public static BoundaryExtractionContext ForEnvironment(
|
||||
string environmentId,
|
||||
bool? isInternetFacing = null,
|
||||
string? networkZone = null) =>
|
||||
string? networkZone = null,
|
||||
TimeProvider? timeProvider = null) =>
|
||||
new()
|
||||
{
|
||||
EnvironmentId = environmentId,
|
||||
IsInternetFacing = isInternetFacing,
|
||||
NetworkZone = networkZone
|
||||
NetworkZone = networkZone,
|
||||
Timestamp = (timeProvider ?? TimeProvider.System).GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,19 +123,22 @@ public sealed class IncrementalReachabilityService : IIncrementalReachabilitySer
|
||||
private readonly IImpactSetCalculator _impactCalculator;
|
||||
private readonly IStateFlipDetector _stateFlipDetector;
|
||||
private readonly ILogger<IncrementalReachabilityService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public IncrementalReachabilityService(
|
||||
IReachabilityCache cache,
|
||||
IGraphDeltaComputer deltaComputer,
|
||||
IImpactSetCalculator impactCalculator,
|
||||
IStateFlipDetector stateFlipDetector,
|
||||
ILogger<IncrementalReachabilityService> logger)
|
||||
ILogger<IncrementalReachabilityService> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||
_deltaComputer = deltaComputer ?? throw new ArgumentNullException(nameof(deltaComputer));
|
||||
_impactCalculator = impactCalculator ?? throw new ArgumentNullException(nameof(impactCalculator));
|
||||
_stateFlipDetector = stateFlipDetector ?? throw new ArgumentNullException(nameof(stateFlipDetector));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -265,7 +268,7 @@ public sealed class IncrementalReachabilityService : IIncrementalReachabilitySer
|
||||
private List<ReachablePairResult> ComputeFullReachability(IncrementalReachabilityRequest request)
|
||||
{
|
||||
var results = new List<ReachablePairResult>();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Build forward adjacency for BFS
|
||||
var adj = new Dictionary<string, List<string>>();
|
||||
@@ -323,7 +326,7 @@ public sealed class IncrementalReachabilityService : IIncrementalReachabilitySer
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var results = new Dictionary<(string, string), ReachablePairResult>();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Copy unaffected results from previous
|
||||
foreach (var prev in previousResults)
|
||||
|
||||
@@ -21,13 +21,16 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly ILogger<PostgresReachabilityCache> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresReachabilityCache(
|
||||
string connectionString,
|
||||
ILogger<PostgresReachabilityCache> logger)
|
||||
ILogger<PostgresReachabilityCache> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -102,7 +105,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
ServiceId = serviceId,
|
||||
GraphHash = graphHash,
|
||||
CachedAt = cachedAt,
|
||||
TimeToLive = expiresAt.HasValue ? expiresAt.Value - DateTimeOffset.UtcNow : null,
|
||||
TimeToLive = expiresAt.HasValue ? expiresAt.Value - _timeProvider.GetUtcNow() : null,
|
||||
ReachablePairs = pairs,
|
||||
EntryPointCount = entryPointCount,
|
||||
SinkCount = sinkCount
|
||||
@@ -143,7 +146,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||
}
|
||||
|
||||
var expiresAt = entry.TimeToLive.HasValue
|
||||
? (object)DateTimeOffset.UtcNow.Add(entry.TimeToLive.Value)
|
||||
? (object)_timeProvider.GetUtcNow().Add(entry.TimeToLive.Value)
|
||||
: DBNull.Value;
|
||||
|
||||
const string insertEntrySql = """
|
||||
|
||||
@@ -225,8 +225,9 @@ public sealed class EdgeBundleBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public EdgeBundle Build()
|
||||
public EdgeBundle Build(TimeProvider? timeProvider = null)
|
||||
{
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
var canonical = _edges
|
||||
.Select(e => e.Trimmed())
|
||||
.OrderBy(e => e.From, StringComparer.Ordinal)
|
||||
@@ -241,7 +242,7 @@ public sealed class EdgeBundleBuilder
|
||||
GraphHash: _graphHash,
|
||||
BundleReason: _bundleReason,
|
||||
Edges: canonical,
|
||||
GeneratedAt: DateTimeOffset.UtcNow,
|
||||
GeneratedAt: tp.GetUtcNow(),
|
||||
CustomReason: _customReason);
|
||||
}
|
||||
|
||||
|
||||
@@ -322,5 +322,5 @@ public sealed record PathExplanationResult
|
||||
/// When the explanation was generated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("generated_at")]
|
||||
public DateTimeOffset GeneratedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class FileSystemCodeContentProvider : ICodeContentProvider
|
||||
return Task.FromResult<string?>(null);
|
||||
}
|
||||
|
||||
return File.ReadAllTextAsync(path, ct);
|
||||
return File.ReadAllTextAsync(path, ct)!;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<string>?> GetLinesAsync(
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace StellaOps.Scanner.Reachability.MiniMap;
|
||||
|
||||
public interface IMiniMapExtractor
|
||||
{
|
||||
ReachabilityMiniMap Extract(RichGraph graph, string vulnerableComponent, int maxPaths = 10);
|
||||
ReachabilityMiniMap Extract(RichGraph graph, string vulnerableComponent, int maxPaths = 10, TimeProvider? timeProvider = null);
|
||||
}
|
||||
|
||||
public sealed class MiniMapExtractor : IMiniMapExtractor
|
||||
@@ -16,16 +16,19 @@ public sealed class MiniMapExtractor : IMiniMapExtractor
|
||||
public ReachabilityMiniMap Extract(
|
||||
RichGraph graph,
|
||||
string vulnerableComponent,
|
||||
int maxPaths = 10)
|
||||
int maxPaths = 10,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
// Find vulnerable component node
|
||||
var vulnNode = graph.Nodes.FirstOrDefault(n =>
|
||||
n.Purl == vulnerableComponent ||
|
||||
n.SymbolId?.Contains(vulnerableComponent) == true);
|
||||
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
|
||||
if (vulnNode is null)
|
||||
{
|
||||
return CreateNotFoundMap(vulnerableComponent);
|
||||
return CreateNotFoundMap(vulnerableComponent, tp);
|
||||
}
|
||||
|
||||
// Find all entrypoints
|
||||
@@ -75,11 +78,11 @@ public sealed class MiniMapExtractor : IMiniMapExtractor
|
||||
State = state,
|
||||
Confidence = confidence,
|
||||
GraphDigest = ComputeGraphDigest(graph),
|
||||
AnalyzedAt = DateTimeOffset.UtcNow
|
||||
AnalyzedAt = tp.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
private static ReachabilityMiniMap CreateNotFoundMap(string vulnerableComponent)
|
||||
private static ReachabilityMiniMap CreateNotFoundMap(string vulnerableComponent, TimeProvider timeProvider)
|
||||
{
|
||||
return new ReachabilityMiniMap
|
||||
{
|
||||
@@ -96,7 +99,7 @@ public sealed class MiniMapExtractor : IMiniMapExtractor
|
||||
State = ReachabilityState.Unknown,
|
||||
Confidence = 0m,
|
||||
GraphDigest = string.Empty,
|
||||
AnalyzedAt = DateTimeOffset.UtcNow
|
||||
AnalyzedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace StellaOps.Scanner.Reachability;
|
||||
/// </summary>
|
||||
public sealed class ReachabilityUnionWriter
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private static readonly JsonWriterOptions JsonOptions = new()
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
@@ -24,6 +26,11 @@ public sealed class ReachabilityUnionWriter
|
||||
SkipValidation = false
|
||||
};
|
||||
|
||||
public ReachabilityUnionWriter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ReachabilityUnionWriteResult> WriteAsync(
|
||||
ReachabilityUnionGraph graph,
|
||||
string outputRoot,
|
||||
@@ -57,7 +64,7 @@ public sealed class ReachabilityUnionWriter
|
||||
File.Delete(factsPath);
|
||||
}
|
||||
|
||||
await WriteMetaAsync(metaPath, nodesInfo, edgesInfo, factsInfo, cancellationToken).ConfigureAwait(false);
|
||||
await WriteMetaAsync(metaPath, nodesInfo, edgesInfo, factsInfo, _timeProvider, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new ReachabilityUnionWriteResult(nodesInfo.ToPublic(), edgesInfo.ToPublic(), factsInfo?.ToPublic(), metaPath);
|
||||
}
|
||||
@@ -387,6 +394,7 @@ public sealed class ReachabilityUnionWriter
|
||||
FileHashInfo nodes,
|
||||
FileHashInfo edges,
|
||||
FileHashInfo? facts,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var stream = File.Create(path);
|
||||
@@ -394,7 +402,7 @@ public sealed class ReachabilityUnionWriter
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("schema", "reachability-union@0.1");
|
||||
writer.WriteString("generated_at", DateTimeOffset.UtcNow.ToString("O"));
|
||||
writer.WriteString("generated_at", timeProvider.GetUtcNow().ToString("O"));
|
||||
writer.WritePropertyName("files");
|
||||
writer.WriteStartArray();
|
||||
WriteMetaFile(writer, nodes);
|
||||
|
||||
@@ -30,15 +30,17 @@ public sealed class SliceCacheOptions
|
||||
public sealed class SliceCache : ISliceCache, IDisposable
|
||||
{
|
||||
private readonly SliceCacheOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ConcurrentDictionary<string, CacheItem> _cache = new(StringComparer.Ordinal);
|
||||
private readonly Timer _evictionTimer;
|
||||
private long _hitCount;
|
||||
private long _missCount;
|
||||
private bool _disposed;
|
||||
|
||||
public SliceCache(IOptions<SliceCacheOptions> options)
|
||||
public SliceCache(IOptions<SliceCacheOptions> options, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_options = options?.Value ?? new SliceCacheOptions();
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_evictionTimer = new Timer(EvictExpired, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
@@ -53,9 +55,10 @@ public sealed class SliceCache : ISliceCache, IDisposable
|
||||
|
||||
if (_cache.TryGetValue(cacheKey, out var item))
|
||||
{
|
||||
if (item.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
if (item.ExpiresAt > now)
|
||||
{
|
||||
item.LastAccessed = DateTimeOffset.UtcNow;
|
||||
item.LastAccessed = now;
|
||||
Interlocked.Increment(ref _hitCount);
|
||||
var result = new CachedSliceResult
|
||||
{
|
||||
@@ -89,7 +92,7 @@ public sealed class SliceCache : ISliceCache, IDisposable
|
||||
EvictLru();
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var item = new CacheItem
|
||||
{
|
||||
Digest = result.SliceDigest,
|
||||
@@ -132,7 +135,7 @@ public sealed class SliceCache : ISliceCache, IDisposable
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var keysToRemove = _cache
|
||||
.Where(kvp => kvp.Value.ExpiresAt <= now)
|
||||
.Select(kvp => kvp.Key)
|
||||
|
||||
@@ -19,7 +19,8 @@ public interface IReachabilityStackEvaluator
|
||||
VulnerableSymbol symbol,
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3);
|
||||
ReachabilityLayer3 layer3,
|
||||
TimeProvider? timeProvider = null);
|
||||
|
||||
/// <summary>
|
||||
/// Derives the verdict from three layers.
|
||||
@@ -53,8 +54,10 @@ public sealed class ReachabilityStackEvaluator : IReachabilityStackEvaluator
|
||||
VulnerableSymbol symbol,
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3)
|
||||
ReachabilityLayer3 layer3,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
var tp = timeProvider ?? TimeProvider.System;
|
||||
var verdict = DeriveVerdict(layer1, layer2, layer3);
|
||||
var explanation = GenerateExplanation(layer1, layer2, layer3, verdict);
|
||||
|
||||
@@ -67,7 +70,7 @@ public sealed class ReachabilityStackEvaluator : IReachabilityStackEvaluator
|
||||
BinaryResolution = layer2,
|
||||
RuntimeGating = layer3,
|
||||
Verdict = verdict,
|
||||
AnalyzedAt = DateTimeOffset.UtcNow,
|
||||
AnalyzedAt = tp.GetUtcNow(),
|
||||
Explanation = explanation
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed class WitnessDsseSigner : IWitnessDsseSigner
|
||||
return WitnessVerifyResult.Failure($"Signature verification failed: {verifyResult.Error?.Message}");
|
||||
}
|
||||
|
||||
return WitnessVerifyResult.Success(witness, matchingSignature.KeyId);
|
||||
return WitnessVerifyResult.Success(witness, matchingSignature.KeyId!);
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or FormatException or InvalidOperationException)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user