feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SmartDiffScoringConfig.cs
|
||||
// Sprint: SPRINT_3500_0004_0001_smart_diff_binary_output
|
||||
// Task: SDIFF-BIN-019 - Implement SmartDiffScoringConfig with presets
|
||||
// Task: SDIFF-BIN-021 - Implement ToDetectorOptions() conversion
|
||||
// Description: Configurable scoring weights for Smart-Diff detection
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.SmartDiff.Detection;
|
||||
|
||||
/// <summary>
|
||||
/// Comprehensive configuration for Smart-Diff scoring.
|
||||
/// Exposes all configurable weights and thresholds for risk detection.
|
||||
/// Per Sprint 3500.4 - Smart-Diff Scoring Configuration.
|
||||
/// </summary>
|
||||
public sealed class SmartDiffScoringConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration name/identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = "default";
|
||||
|
||||
/// <summary>
|
||||
/// Configuration version for compatibility tracking.
|
||||
/// </summary>
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; init; } = "1.0";
|
||||
|
||||
#region Rule R1: Reachability
|
||||
|
||||
/// <summary>
|
||||
/// Weight for reachability flip from unreachable to reachable (risk increase).
|
||||
/// </summary>
|
||||
[JsonPropertyName("reachabilityFlipUpWeight")]
|
||||
public double ReachabilityFlipUpWeight { get; init; } = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for reachability flip from reachable to unreachable (risk decrease).
|
||||
/// </summary>
|
||||
[JsonPropertyName("reachabilityFlipDownWeight")]
|
||||
public double ReachabilityFlipDownWeight { get; init; } = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to consider lattice confidence in reachability scoring.
|
||||
/// </summary>
|
||||
[JsonPropertyName("useLatticeConfidence")]
|
||||
public bool UseLatticeConfidence { get; init; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rule R2: VEX Status
|
||||
|
||||
/// <summary>
|
||||
/// Weight for VEX status flip to affected.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vexFlipToAffectedWeight")]
|
||||
public double VexFlipToAffectedWeight { get; init; } = 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for VEX status flip to not_affected.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vexFlipToNotAffectedWeight")]
|
||||
public double VexFlipToNotAffectedWeight { get; init; } = 0.7;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for VEX status flip to fixed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vexFlipToFixedWeight")]
|
||||
public double VexFlipToFixedWeight { get; init; } = 0.6;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for VEX status flip to under_investigation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("vexFlipToUnderInvestigationWeight")]
|
||||
public double VexFlipToUnderInvestigationWeight { get; init; } = 0.3;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rule R3: Affected Range
|
||||
|
||||
/// <summary>
|
||||
/// Weight for entering the affected version range.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rangeEntryWeight")]
|
||||
public double RangeEntryWeight { get; init; } = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for exiting the affected version range.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rangeExitWeight")]
|
||||
public double RangeExitWeight { get; init; } = 0.6;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rule R4: Intelligence Signals
|
||||
|
||||
/// <summary>
|
||||
/// Weight for KEV (Known Exploited Vulnerability) addition.
|
||||
/// </summary>
|
||||
[JsonPropertyName("kevAddedWeight")]
|
||||
public double KevAddedWeight { get; init; } = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for KEV removal.
|
||||
/// </summary>
|
||||
[JsonPropertyName("kevRemovedWeight")]
|
||||
public double KevRemovedWeight { get; init; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for EPSS threshold crossing.
|
||||
/// </summary>
|
||||
[JsonPropertyName("epssThresholdWeight")]
|
||||
public double EpssThresholdWeight { get; init; } = 0.6;
|
||||
|
||||
/// <summary>
|
||||
/// EPSS score threshold for R4 detection (0.0 - 1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("epssThreshold")]
|
||||
public double EpssThreshold { get; init; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Weight for policy decision flip.
|
||||
/// </summary>
|
||||
[JsonPropertyName("policyFlipWeight")]
|
||||
public double PolicyFlipWeight { get; init; } = 0.7;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hardening Detection
|
||||
|
||||
/// <summary>
|
||||
/// Weight for hardening regression detection.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hardeningRegressionWeight")]
|
||||
public double HardeningRegressionWeight { get; init; } = 0.7;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum hardening score difference to trigger a finding.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hardeningScoreThreshold")]
|
||||
public double HardeningScoreThreshold { get; init; } = 0.2;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include hardening flags in diff output.
|
||||
/// </summary>
|
||||
[JsonPropertyName("includeHardeningFlags")]
|
||||
public bool IncludeHardeningFlags { get; init; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Priority Score Factors
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier applied when finding is in KEV.
|
||||
/// </summary>
|
||||
[JsonPropertyName("kevBoost")]
|
||||
public double KevBoost { get; init; } = 1.5;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum priority score to emit a finding.
|
||||
/// </summary>
|
||||
[JsonPropertyName("minPriorityScore")]
|
||||
public double MinPriorityScore { get; init; } = 0.1;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for "high priority" classification.
|
||||
/// </summary>
|
||||
[JsonPropertyName("highPriorityThreshold")]
|
||||
public double HighPriorityThreshold { get; init; } = 0.7;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for "critical priority" classification.
|
||||
/// </summary>
|
||||
[JsonPropertyName("criticalPriorityThreshold")]
|
||||
public double CriticalPriorityThreshold { get; init; } = 0.9;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Presets
|
||||
|
||||
/// <summary>
|
||||
/// Default configuration - balanced detection.
|
||||
/// </summary>
|
||||
public static SmartDiffScoringConfig Default => new()
|
||||
{
|
||||
Name = "default"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Security-focused preset - aggressive detection, lower thresholds.
|
||||
/// </summary>
|
||||
public static SmartDiffScoringConfig SecurityFocused => new()
|
||||
{
|
||||
Name = "security-focused",
|
||||
ReachabilityFlipUpWeight = 1.2,
|
||||
VexFlipToAffectedWeight = 1.0,
|
||||
KevAddedWeight = 1.5,
|
||||
EpssThreshold = 0.3,
|
||||
EpssThresholdWeight = 0.8,
|
||||
HardeningRegressionWeight = 0.9,
|
||||
HardeningScoreThreshold = 0.15,
|
||||
MinPriorityScore = 0.05,
|
||||
HighPriorityThreshold = 0.5,
|
||||
CriticalPriorityThreshold = 0.8
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Compliance-focused preset - stricter thresholds for regulated environments.
|
||||
/// </summary>
|
||||
public static SmartDiffScoringConfig ComplianceFocused => new()
|
||||
{
|
||||
Name = "compliance-focused",
|
||||
ReachabilityFlipUpWeight = 1.0,
|
||||
VexFlipToAffectedWeight = 1.0,
|
||||
VexFlipToNotAffectedWeight = 0.9,
|
||||
KevAddedWeight = 2.0,
|
||||
EpssThreshold = 0.2,
|
||||
PolicyFlipWeight = 1.0,
|
||||
HardeningRegressionWeight = 1.0,
|
||||
HardeningScoreThreshold = 0.1,
|
||||
MinPriorityScore = 0.0,
|
||||
HighPriorityThreshold = 0.4,
|
||||
CriticalPriorityThreshold = 0.7
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Developer-friendly preset - reduced noise, focus on actionable changes.
|
||||
/// </summary>
|
||||
public static SmartDiffScoringConfig DeveloperFriendly => new()
|
||||
{
|
||||
Name = "developer-friendly",
|
||||
ReachabilityFlipUpWeight = 0.8,
|
||||
VexFlipToAffectedWeight = 0.7,
|
||||
KevAddedWeight = 1.0,
|
||||
EpssThreshold = 0.7,
|
||||
EpssThresholdWeight = 0.4,
|
||||
HardeningRegressionWeight = 0.5,
|
||||
HardeningScoreThreshold = 0.3,
|
||||
MinPriorityScore = 0.2,
|
||||
HighPriorityThreshold = 0.8,
|
||||
CriticalPriorityThreshold = 0.95
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get a preset configuration by name.
|
||||
/// </summary>
|
||||
public static SmartDiffScoringConfig GetPreset(string name) => name.ToLowerInvariant() switch
|
||||
{
|
||||
"default" => Default,
|
||||
"security-focused" or "security" => SecurityFocused,
|
||||
"compliance-focused" or "compliance" => ComplianceFocused,
|
||||
"developer-friendly" or "developer" => DeveloperFriendly,
|
||||
_ => throw new ArgumentException($"Unknown scoring preset: {name}")
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conversion Methods
|
||||
|
||||
/// <summary>
|
||||
/// Convert to MaterialRiskChangeOptions for use with the detector.
|
||||
/// Task: SDIFF-BIN-021.
|
||||
/// </summary>
|
||||
public MaterialRiskChangeOptions ToDetectorOptions() => new()
|
||||
{
|
||||
ReachabilityFlipUpWeight = ReachabilityFlipUpWeight,
|
||||
ReachabilityFlipDownWeight = ReachabilityFlipDownWeight,
|
||||
VexFlipToAffectedWeight = VexFlipToAffectedWeight,
|
||||
VexFlipToNotAffectedWeight = VexFlipToNotAffectedWeight,
|
||||
RangeEntryWeight = RangeEntryWeight,
|
||||
RangeExitWeight = RangeExitWeight,
|
||||
KevAddedWeight = KevAddedWeight,
|
||||
KevRemovedWeight = KevRemovedWeight,
|
||||
EpssThreshold = EpssThreshold,
|
||||
EpssThresholdWeight = EpssThresholdWeight,
|
||||
PolicyFlipWeight = PolicyFlipWeight
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a detector configured with these options.
|
||||
/// </summary>
|
||||
public MaterialRiskChangeDetector CreateDetector() => new(ToDetectorOptions());
|
||||
|
||||
/// <summary>
|
||||
/// Validate configuration values.
|
||||
/// </summary>
|
||||
public SmartDiffScoringConfigValidation Validate()
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
// Weight validations (should be 0.0 - 2.0)
|
||||
ValidateWeight(nameof(ReachabilityFlipUpWeight), ReachabilityFlipUpWeight, errors);
|
||||
ValidateWeight(nameof(ReachabilityFlipDownWeight), ReachabilityFlipDownWeight, errors);
|
||||
ValidateWeight(nameof(VexFlipToAffectedWeight), VexFlipToAffectedWeight, errors);
|
||||
ValidateWeight(nameof(VexFlipToNotAffectedWeight), VexFlipToNotAffectedWeight, errors);
|
||||
ValidateWeight(nameof(RangeEntryWeight), RangeEntryWeight, errors);
|
||||
ValidateWeight(nameof(RangeExitWeight), RangeExitWeight, errors);
|
||||
ValidateWeight(nameof(KevAddedWeight), KevAddedWeight, errors);
|
||||
ValidateWeight(nameof(KevRemovedWeight), KevRemovedWeight, errors);
|
||||
ValidateWeight(nameof(EpssThresholdWeight), EpssThresholdWeight, errors);
|
||||
ValidateWeight(nameof(PolicyFlipWeight), PolicyFlipWeight, errors);
|
||||
ValidateWeight(nameof(HardeningRegressionWeight), HardeningRegressionWeight, errors);
|
||||
|
||||
// Threshold validations (should be 0.0 - 1.0)
|
||||
ValidateThreshold(nameof(EpssThreshold), EpssThreshold, errors);
|
||||
ValidateThreshold(nameof(HardeningScoreThreshold), HardeningScoreThreshold, errors);
|
||||
ValidateThreshold(nameof(MinPriorityScore), MinPriorityScore, errors);
|
||||
ValidateThreshold(nameof(HighPriorityThreshold), HighPriorityThreshold, errors);
|
||||
ValidateThreshold(nameof(CriticalPriorityThreshold), CriticalPriorityThreshold, errors);
|
||||
|
||||
// Logical validations
|
||||
if (HighPriorityThreshold >= CriticalPriorityThreshold)
|
||||
{
|
||||
errors.Add($"HighPriorityThreshold ({HighPriorityThreshold}) must be less than CriticalPriorityThreshold ({CriticalPriorityThreshold})");
|
||||
}
|
||||
|
||||
if (MinPriorityScore >= HighPriorityThreshold)
|
||||
{
|
||||
errors.Add($"MinPriorityScore ({MinPriorityScore}) should be less than HighPriorityThreshold ({HighPriorityThreshold})");
|
||||
}
|
||||
|
||||
return new SmartDiffScoringConfigValidation(errors.Count == 0, [.. errors]);
|
||||
}
|
||||
|
||||
private static void ValidateWeight(string name, double value, List<string> errors)
|
||||
{
|
||||
if (value < 0.0 || value > 2.0)
|
||||
{
|
||||
errors.Add($"{name} must be between 0.0 and 2.0, got {value}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateThreshold(string name, double value, List<string> errors)
|
||||
{
|
||||
if (value < 0.0 || value > 1.0)
|
||||
{
|
||||
errors.Add($"{name} must be between 0.0 and 1.0, got {value}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of scoring config validation.
|
||||
/// </summary>
|
||||
public sealed record SmartDiffScoringConfigValidation(
|
||||
[property: JsonPropertyName("isValid")] bool IsValid,
|
||||
[property: JsonPropertyName("errors")] string[] Errors);
|
||||
Reference in New Issue
Block a user