finish secrets finding work and audit remarks work save

This commit is contained in:
StellaOps Bot
2026-01-04 21:48:13 +02:00
parent 75611a505f
commit 8862e112c4
157 changed files with 11702 additions and 416 deletions

View File

@@ -57,11 +57,13 @@ public interface IBaselineAnalyzer
public sealed class BaselineAnalyzer : IBaselineAnalyzer
{
private readonly ILogger<BaselineAnalyzer> _logger;
private readonly TimeProvider _timeProvider;
private readonly Dictionary<string, Regex> _compiledPatterns = new();
public BaselineAnalyzer(ILogger<BaselineAnalyzer> logger)
public BaselineAnalyzer(ILogger<BaselineAnalyzer> logger, TimeProvider? timeProvider = null)
{
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<BaselineReport> AnalyzeAsync(
@@ -97,7 +99,7 @@ public sealed class BaselineAnalyzer : IBaselineAnalyzer
{
ReportId = Guid.NewGuid(),
ScanId = context.ScanId,
GeneratedAt = DateTimeOffset.UtcNow,
GeneratedAt = _timeProvider.GetUtcNow(),
ConfigUsed = context.Config.ConfigId,
EntryPoints = entryPoints.ToImmutableArray(),
Statistics = statistics,

View File

@@ -56,7 +56,8 @@ public sealed record BinaryAnalysisResult(
string binaryPath,
string binaryHash,
BinaryArchitecture architecture = BinaryArchitecture.Unknown,
BinaryFormat format = BinaryFormat.Unknown) => new(
BinaryFormat format = BinaryFormat.Unknown,
TimeProvider? timeProvider = null) => new(
binaryPath,
binaryHash,
architecture,
@@ -66,7 +67,7 @@ public sealed record BinaryAnalysisResult(
ImmutableArray<SourceCorrelation>.Empty,
ImmutableArray<VulnerableFunctionMatch>.Empty,
BinaryAnalysisMetrics.Empty,
DateTimeOffset.UtcNow);
(timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Gets functions at high-confidence correlation.
@@ -324,18 +325,22 @@ public sealed class BinaryAnalysisResultBuilder
private readonly Dictionary<long, SymbolInfo> _symbols = new();
private readonly List<SourceCorrelation> _correlations = new();
private readonly List<VulnerableFunctionMatch> _vulnerableMatches = new();
private readonly DateTimeOffset _startTime = DateTimeOffset.UtcNow;
private readonly TimeProvider _timeProvider;
private readonly DateTimeOffset _startTime;
public BinaryAnalysisResultBuilder(
string binaryPath,
string binaryHash,
BinaryArchitecture architecture = BinaryArchitecture.Unknown,
BinaryFormat format = BinaryFormat.Unknown)
BinaryFormat format = BinaryFormat.Unknown,
TimeProvider? timeProvider = null)
{
_binaryPath = binaryPath;
_binaryHash = binaryHash;
_architecture = architecture;
_format = format;
_timeProvider = timeProvider ?? TimeProvider.System;
_startTime = _timeProvider.GetUtcNow();
}
/// <summary>
@@ -379,7 +384,8 @@ public sealed class BinaryAnalysisResultBuilder
/// </summary>
public BinaryAnalysisResult Build()
{
var duration = DateTimeOffset.UtcNow - _startTime;
var now = _timeProvider.GetUtcNow();
var duration = now - _startTime;
var metrics = new BinaryAnalysisMetrics(
TotalFunctions: _functions.Count,
@@ -401,6 +407,6 @@ public sealed class BinaryAnalysisResultBuilder
_correlations.OrderBy(c => c.BinaryOffset).ToImmutableArray(),
_vulnerableMatches.OrderByDescending(m => m.Severity).ToImmutableArray(),
metrics,
DateTimeOffset.UtcNow);
now);
}
}

View File

@@ -16,6 +16,7 @@ public sealed class BinaryIntelligenceAnalyzer
private readonly ISymbolRecovery _symbolRecovery;
private readonly VulnerableFunctionMatcher _vulnerabilityMatcher;
private readonly BinaryIntelligenceOptions _options;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new binary intelligence analyzer.
@@ -25,12 +26,14 @@ public sealed class BinaryIntelligenceAnalyzer
IFingerprintIndex? fingerprintIndex = null,
ISymbolRecovery? symbolRecovery = null,
VulnerableFunctionMatcher? vulnerabilityMatcher = null,
BinaryIntelligenceOptions? options = null)
BinaryIntelligenceOptions? options = null,
TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_fingerprintGenerator = fingerprintGenerator ?? new CombinedFingerprintGenerator();
_fingerprintIndex = fingerprintIndex ?? new InMemoryFingerprintIndex();
_fingerprintIndex = fingerprintIndex ?? new InMemoryFingerprintIndex(_timeProvider);
_symbolRecovery = symbolRecovery ?? new PatternBasedSymbolRecovery();
_vulnerabilityMatcher = vulnerabilityMatcher ?? new VulnerableFunctionMatcher(_fingerprintIndex);
_vulnerabilityMatcher = vulnerabilityMatcher ?? new VulnerableFunctionMatcher(_fingerprintIndex, timeProvider: _timeProvider);
_options = options ?? BinaryIntelligenceOptions.Default;
}
@@ -53,7 +56,7 @@ public sealed class BinaryIntelligenceAnalyzer
CancellationToken cancellationToken = default)
{
var stopwatch = Stopwatch.StartNew();
var builder = new BinaryAnalysisResultBuilder(binaryPath, binaryHash, architecture, format);
var builder = new BinaryAnalysisResultBuilder(binaryPath, binaryHash, architecture, format, _timeProvider);
// Phase 1: Generate fingerprints for all functions
var fingerprints = new Dictionary<long, CodeFingerprint>();
@@ -186,7 +189,7 @@ public sealed class BinaryIntelligenceAnalyzer
SourceLine: null,
VulnerabilityIds: vulnerabilityIds?.ToImmutableArray() ?? ImmutableArray<string>.Empty,
Similarity: 1.0f,
MatchedAt: DateTimeOffset.UtcNow);
MatchedAt: _timeProvider.GetUtcNow());
if (await _fingerprintIndex.AddAsync(entry, cancellationToken))
{

View File

@@ -14,6 +14,7 @@ public sealed class FingerprintCorpusBuilder
private readonly IFingerprintGenerator _fingerprintGenerator;
private readonly IFingerprintIndex _targetIndex;
private readonly FingerprintCorpusOptions _options;
private readonly TimeProvider _timeProvider;
private readonly List<CorpusBuildRecord> _buildHistory = new();
/// <summary>
@@ -22,11 +23,13 @@ public sealed class FingerprintCorpusBuilder
public FingerprintCorpusBuilder(
IFingerprintIndex targetIndex,
IFingerprintGenerator? fingerprintGenerator = null,
FingerprintCorpusOptions? options = null)
FingerprintCorpusOptions? options = null,
TimeProvider? timeProvider = null)
{
_targetIndex = targetIndex;
_fingerprintGenerator = fingerprintGenerator ?? new CombinedFingerprintGenerator();
_options = options ?? FingerprintCorpusOptions.Default;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
@@ -41,7 +44,7 @@ public sealed class FingerprintCorpusBuilder
IReadOnlyList<FunctionSignature> functions,
CancellationToken cancellationToken = default)
{
var startTime = DateTimeOffset.UtcNow;
var startTime = _timeProvider.GetUtcNow();
var indexed = 0;
var skipped = 0;
var duplicates = 0;
@@ -93,7 +96,7 @@ public sealed class FingerprintCorpusBuilder
SourceLine: null,
VulnerabilityIds: package.VulnerabilityIds,
Similarity: 1.0f,
MatchedAt: DateTimeOffset.UtcNow);
MatchedAt: _timeProvider.GetUtcNow());
var added = await _targetIndex.AddAsync(entry, cancellationToken);
@@ -119,9 +122,9 @@ public sealed class FingerprintCorpusBuilder
Skipped: skipped,
Duplicates: duplicates,
Errors: errors.ToImmutableArray(),
Duration: DateTimeOffset.UtcNow - startTime);
Duration: _timeProvider.GetUtcNow() - startTime);
_buildHistory.Add(new CorpusBuildRecord(package.Purl, package.Version, result, DateTimeOffset.UtcNow));
_buildHistory.Add(new CorpusBuildRecord(package.Purl, package.Version, result, _timeProvider.GetUtcNow()));
return result;
}
@@ -207,7 +210,7 @@ public sealed class FingerprintCorpusBuilder
// For now, export build history as a summary
var data = new CorpusExportData
{
ExportedAt = DateTimeOffset.UtcNow,
ExportedAt = _timeProvider.GetUtcNow(),
Statistics = _targetIndex.GetStatistics(),
Entries = Array.Empty<CorpusEntryData>() // Full export would need index enumeration
};

View File

@@ -140,7 +140,18 @@ public sealed class InMemoryFingerprintIndex : IFingerprintIndex
private readonly ConcurrentDictionary<FingerprintAlgorithm, List<FingerprintMatch>> _algorithmIndex = new();
private readonly HashSet<string> _packages = new();
private readonly object _packagesLock = new();
private DateTimeOffset _lastUpdated = DateTimeOffset.UtcNow;
private readonly TimeProvider _timeProvider;
private DateTimeOffset _lastUpdated;
/// <summary>
/// Creates a new in-memory fingerprint index.
/// </summary>
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
public InMemoryFingerprintIndex(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_lastUpdated = _timeProvider.GetUtcNow();
}
/// <inheritdoc/>
public int Count => _exactIndex.Count;
@@ -182,7 +193,7 @@ public sealed class InMemoryFingerprintIndex : IFingerprintIndex
_packages.Add(match.SourcePackage);
}
_lastUpdated = DateTimeOffset.UtcNow;
_lastUpdated = _timeProvider.GetUtcNow();
}
return Task.FromResult(added);
@@ -302,7 +313,7 @@ public sealed class InMemoryFingerprintIndex : IFingerprintIndex
SourceLine: null,
VulnerabilityIds: ImmutableArray<string>.Empty,
Similarity: 1.0f,
MatchedAt: DateTimeOffset.UtcNow);
MatchedAt: _timeProvider.GetUtcNow());
return AddAsync(match, cancellationToken).ContinueWith(_ => { }, cancellationToken);
}
@@ -313,9 +324,20 @@ public sealed class InMemoryFingerprintIndex : IFingerprintIndex
/// </summary>
public sealed class VulnerableFingerprintIndex : IFingerprintIndex
{
private readonly InMemoryFingerprintIndex _baseIndex = new();
private readonly InMemoryFingerprintIndex _baseIndex;
private readonly TimeProvider _timeProvider;
private readonly ConcurrentDictionary<string, VulnerabilityInfo> _vulnerabilities = new();
/// <summary>
/// Creates a new vulnerability-aware fingerprint index.
/// </summary>
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
public VulnerableFingerprintIndex(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_baseIndex = new InMemoryFingerprintIndex(_timeProvider);
}
/// <inheritdoc/>
public int Count => _baseIndex.Count;
@@ -344,7 +366,7 @@ public sealed class VulnerableFingerprintIndex : IFingerprintIndex
SourceLine: null,
VulnerabilityIds: ImmutableArray.Create(vulnerabilityId),
Similarity: 1.0f,
MatchedAt: DateTimeOffset.UtcNow);
MatchedAt: _timeProvider.GetUtcNow());
var added = await _baseIndex.AddAsync(match, cancellationToken);

View File

@@ -11,16 +11,19 @@ public sealed class VulnerableFunctionMatcher
{
private readonly IFingerprintIndex _index;
private readonly VulnerableMatcherOptions _options;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new vulnerable function matcher.
/// </summary>
public VulnerableFunctionMatcher(
IFingerprintIndex index,
VulnerableMatcherOptions? options = null)
VulnerableMatcherOptions? options = null,
TimeProvider? timeProvider = null)
{
_index = index;
_options = options ?? VulnerableMatcherOptions.Default;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
@@ -165,7 +168,7 @@ public sealed class VulnerableFunctionMatcher
SourceLine: null,
VulnerabilityIds: ImmutableArray.Create(vulnerabilityId),
Similarity: 1.0f,
MatchedAt: DateTimeOffset.UtcNow);
MatchedAt: _timeProvider.GetUtcNow());
return await _index.AddAsync(entry, cancellationToken);
}

View File

@@ -11,12 +11,13 @@ public sealed class CompositeRiskScorer : IRiskScorer
{
private readonly ImmutableArray<IRiskContributor> _contributors;
private readonly CompositeRiskScorerOptions _options;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a composite scorer with default contributors.
/// </summary>
public CompositeRiskScorer(CompositeRiskScorerOptions? options = null)
: this(GetDefaultContributors(), options)
public CompositeRiskScorer(CompositeRiskScorerOptions? options = null, TimeProvider? timeProvider = null)
: this(GetDefaultContributors(), options, timeProvider)
{
}
@@ -25,10 +26,12 @@ public sealed class CompositeRiskScorer : IRiskScorer
/// </summary>
public CompositeRiskScorer(
IEnumerable<IRiskContributor> contributors,
CompositeRiskScorerOptions? options = null)
CompositeRiskScorerOptions? options = null,
TimeProvider? timeProvider = null)
{
_contributors = contributors.ToImmutableArray();
_options = options ?? CompositeRiskScorerOptions.Default;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc/>
@@ -66,7 +69,7 @@ public sealed class CompositeRiskScorer : IRiskScorer
Factors: allFactors.ToImmutableArray(),
BusinessContext: businessContext,
Recommendations: recommendations,
AssessedAt: DateTimeOffset.UtcNow);
AssessedAt: _timeProvider.GetUtcNow());
}
private RiskScore ComputeOverallScore(
@@ -75,7 +78,7 @@ public sealed class CompositeRiskScorer : IRiskScorer
{
if (factors.Count == 0)
{
return RiskScore.Zero;
return RiskScore.Zero(_timeProvider);
}
// Weighted average of factor contributions
@@ -106,7 +109,7 @@ public sealed class CompositeRiskScorer : IRiskScorer
OverallScore: baseScore,
Category: primaryCategory,
Confidence: confidence,
ComputedAt: DateTimeOffset.UtcNow);
ComputedAt: _timeProvider.GetUtcNow());
}
private float ComputeConfidence(IReadOnlyList<RiskFactor> factors)
@@ -217,6 +220,17 @@ public sealed record CompositeRiskScorerOptions(
/// </summary>
public sealed class RiskExplainer
{
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new risk explainer.
/// </summary>
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
public RiskExplainer(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Generates a summary explanation for a risk assessment.
/// </summary>
@@ -268,7 +282,7 @@ public sealed class RiskExplainer
Confidence: assessment.OverallScore.Confidence,
TopFactors: ExplainFactors(assessment),
Recommendations: assessment.Recommendations,
GeneratedAt: DateTimeOffset.UtcNow);
GeneratedAt: _timeProvider.GetUtcNow());
}
private static string CategoryToString(RiskCategory category) => category switch
@@ -313,6 +327,17 @@ public sealed record RiskReport(
/// </summary>
public sealed class RiskAggregator
{
private readonly TimeProvider _timeProvider;
/// <summary>
/// Creates a new risk aggregator.
/// </summary>
/// <param name="timeProvider">Optional time provider for deterministic timestamps.</param>
public RiskAggregator(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Aggregates assessments for a fleet-level view.
/// </summary>
@@ -322,7 +347,7 @@ public sealed class RiskAggregator
if (assessmentList.Count == 0)
{
return FleetRiskSummary.Empty;
return FleetRiskSummary.CreateEmpty(_timeProvider);
}
var distribution = assessmentList
@@ -349,7 +374,7 @@ public sealed class RiskAggregator
Distribution: distribution.ToImmutableDictionary(),
CategoryBreakdown: categoryBreakdown.ToImmutableDictionary(),
TopRisks: topRisks,
AggregatedAt: DateTimeOffset.UtcNow);
AggregatedAt: _timeProvider.GetUtcNow());
}
}
@@ -373,16 +398,22 @@ public sealed record FleetRiskSummary(
DateTimeOffset AggregatedAt)
{
/// <summary>
/// Empty summary.
/// Empty summary with specified timestamp.
/// </summary>
public static FleetRiskSummary Empty => new(
public static FleetRiskSummary CreateEmpty(TimeProvider? timeProvider = null) => new(
TotalSubjects: 0,
AverageScore: 0,
AverageConfidence: 0,
Distribution: ImmutableDictionary<RiskLevel, int>.Empty,
CategoryBreakdown: ImmutableDictionary<RiskCategory, int>.Empty,
TopRisks: ImmutableArray<RiskSummaryItem>.Empty,
AggregatedAt: DateTimeOffset.UtcNow);
AggregatedAt: (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Empty summary (uses current time).
/// </summary>
[Obsolete("Use CreateEmpty(TimeProvider) for deterministic timestamps")]
public static FleetRiskSummary Empty => CreateEmpty();
/// <summary>
/// Count of critical/high risk subjects.

View File

@@ -20,31 +20,32 @@ public sealed record RiskScore(
/// <summary>
/// Creates a zero risk score.
/// </summary>
public static RiskScore Zero => new(0.0f, RiskCategory.Unknown, 1.0f, DateTimeOffset.UtcNow);
public static RiskScore Zero(TimeProvider? timeProvider = null)
=> new(0.0f, RiskCategory.Unknown, 1.0f, (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Creates a critical risk score.
/// </summary>
public static RiskScore Critical(RiskCategory category, float confidence = 0.9f)
=> new(1.0f, category, confidence, DateTimeOffset.UtcNow);
public static RiskScore Critical(RiskCategory category, float confidence = 0.9f, TimeProvider? timeProvider = null)
=> new(1.0f, category, confidence, (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Creates a high risk score.
/// </summary>
public static RiskScore High(RiskCategory category, float confidence = 0.85f)
=> new(0.85f, category, confidence, DateTimeOffset.UtcNow);
public static RiskScore High(RiskCategory category, float confidence = 0.85f, TimeProvider? timeProvider = null)
=> new(0.85f, category, confidence, (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Creates a medium risk score.
/// </summary>
public static RiskScore Medium(RiskCategory category, float confidence = 0.8f)
=> new(0.5f, category, confidence, DateTimeOffset.UtcNow);
public static RiskScore Medium(RiskCategory category, float confidence = 0.8f, TimeProvider? timeProvider = null)
=> new(0.5f, category, confidence, (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Creates a low risk score.
/// </summary>
public static RiskScore Low(RiskCategory category, float confidence = 0.75f)
=> new(0.2f, category, confidence, DateTimeOffset.UtcNow);
public static RiskScore Low(RiskCategory category, float confidence = 0.75f, TimeProvider? timeProvider = null)
=> new(0.2f, category, confidence, (timeProvider ?? TimeProvider.System).GetUtcNow());
/// <summary>
/// Descriptive risk level based on score.
@@ -349,14 +350,18 @@ public sealed record RiskAssessment(
/// <summary>
/// Creates an empty assessment for a subject with no risk data.
/// </summary>
public static RiskAssessment Empty(string subjectId, SubjectType subjectType) => new(
SubjectId: subjectId,
SubjectType: subjectType,
OverallScore: RiskScore.Zero,
Factors: ImmutableArray<RiskFactor>.Empty,
BusinessContext: null,
Recommendations: ImmutableArray<string>.Empty,
AssessedAt: DateTimeOffset.UtcNow);
public static RiskAssessment Empty(string subjectId, SubjectType subjectType, TimeProvider? timeProvider = null)
{
var tp = timeProvider ?? TimeProvider.System;
return new(
SubjectId: subjectId,
SubjectType: subjectType,
OverallScore: RiskScore.Zero(tp),
Factors: ImmutableArray<RiskFactor>.Empty,
BusinessContext: null,
Recommendations: ImmutableArray<string>.Empty,
AssessedAt: tp.GetUtcNow());
}
}
/// <summary>

View File

@@ -16,21 +16,25 @@ public sealed class SemanticEntryTraceAnalyzer : ISemanticEntryTraceAnalyzer
private readonly IEntryTraceAnalyzer _baseAnalyzer;
private readonly SemanticEntrypointOrchestrator _orchestrator;
private readonly ILogger<SemanticEntryTraceAnalyzer> _logger;
private readonly TimeProvider _timeProvider;
public SemanticEntryTraceAnalyzer(
IEntryTraceAnalyzer baseAnalyzer,
SemanticEntrypointOrchestrator orchestrator,
ILogger<SemanticEntryTraceAnalyzer> logger)
ILogger<SemanticEntryTraceAnalyzer> logger,
TimeProvider? timeProvider = null)
{
_baseAnalyzer = baseAnalyzer ?? throw new ArgumentNullException(nameof(baseAnalyzer));
_orchestrator = orchestrator ?? throw new ArgumentNullException(nameof(orchestrator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
public SemanticEntryTraceAnalyzer(
IEntryTraceAnalyzer baseAnalyzer,
ILogger<SemanticEntryTraceAnalyzer> logger)
: this(baseAnalyzer, new SemanticEntrypointOrchestrator(), logger)
ILogger<SemanticEntryTraceAnalyzer> logger,
TimeProvider? timeProvider = null)
: this(baseAnalyzer, new SemanticEntrypointOrchestrator(), logger, timeProvider)
{
}
@@ -52,7 +56,7 @@ public sealed class SemanticEntryTraceAnalyzer : ISemanticEntryTraceAnalyzer
var traceResult = new EntryTraceResult(
context.ScanId,
context.ImageDigest,
DateTimeOffset.UtcNow,
_timeProvider.GetUtcNow(),
graph,
SerializeToNdjson(graph));
@@ -98,7 +102,7 @@ public sealed class SemanticEntryTraceAnalyzer : ISemanticEntryTraceAnalyzer
TraceResult = traceResult,
SemanticEntrypoint = semanticResult,
AnalysisResult = analysisResult,
AnalyzedAt = DateTimeOffset.UtcNow
AnalyzedAt = _timeProvider.GetUtcNow()
};
}