Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Waf\MachineLearning\ValueObjects;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\Waf\MachineLearning\AnomalyType;
|
||||
use App\Framework\Waf\MachineLearning\BehaviorType;
|
||||
|
||||
/**
|
||||
* Represents a detected behavioral anomaly
|
||||
*/
|
||||
final readonly class AnomalyDetection
|
||||
{
|
||||
public function __construct(
|
||||
public AnomalyType $type,
|
||||
public BehaviorType $behaviorType,
|
||||
public Percentage $confidence,
|
||||
public float $anomalyScore,
|
||||
public string $description,
|
||||
public array $features,
|
||||
public array $evidence,
|
||||
public ?string $clientId = null,
|
||||
public ?string $sessionId = null,
|
||||
public ?Timestamp $detectedAt = null,
|
||||
public ?Duration $analysisWindow = null,
|
||||
public array $metadata = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create anomaly detection with automatic confidence calculation
|
||||
*/
|
||||
public static function create(
|
||||
AnomalyType $type,
|
||||
BehaviorType $behaviorType,
|
||||
float $anomalyScore,
|
||||
string $description,
|
||||
array $features = [],
|
||||
array $evidence = []
|
||||
): self {
|
||||
// Calculate confidence based on anomaly score and feature consistency
|
||||
$baseConfidence = min($anomalyScore * 100, 100.0);
|
||||
|
||||
// Adjust confidence based on feature agreement
|
||||
if (! empty($features)) {
|
||||
$featureAnomalyScores = array_map(
|
||||
fn (BehaviorFeature $feature) => $feature->getAnomalyScore(),
|
||||
$features
|
||||
);
|
||||
|
||||
$meanFeatureScore = array_sum($featureAnomalyScores) / count($featureAnomalyScores);
|
||||
$featureConsistency = 1.0 - (abs($anomalyScore - $meanFeatureScore) / max($anomalyScore, 0.01));
|
||||
|
||||
$baseConfidence *= $featureConsistency;
|
||||
}
|
||||
|
||||
$confidence = Percentage::from(max(0.0, min(100.0, $baseConfidence)));
|
||||
|
||||
return new self(
|
||||
type: $type,
|
||||
behaviorType: $behaviorType,
|
||||
confidence: $confidence,
|
||||
anomalyScore: $anomalyScore,
|
||||
description: $description,
|
||||
features: $features,
|
||||
evidence: $evidence,
|
||||
detectedAt: Timestamp::fromFloat(microtime(true))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create frequency spike anomaly
|
||||
*/
|
||||
public static function frequencySpike(
|
||||
float $currentRate,
|
||||
float $baseline,
|
||||
float $threshold = 3.0,
|
||||
?string $clientId = null
|
||||
): self {
|
||||
$ratio = $baseline > 0 ? $currentRate / $baseline : $currentRate;
|
||||
$anomalyScore = min(($ratio - 1.0) / $threshold, 1.0);
|
||||
|
||||
return self::create(
|
||||
type: AnomalyType::FREQUENCY_SPIKE,
|
||||
behaviorType: BehaviorType::REQUEST_FREQUENCY,
|
||||
anomalyScore: $anomalyScore,
|
||||
description: "Request frequency spike detected: {$currentRate}/s (baseline: {$baseline}/s, ratio: " . round($ratio, 2) . "x)",
|
||||
evidence: [
|
||||
'current_rate' => $currentRate,
|
||||
'baseline_rate' => $baseline,
|
||||
'spike_ratio' => $ratio,
|
||||
'threshold' => $threshold,
|
||||
]
|
||||
);
|
||||
|
||||
return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create geographic anomaly
|
||||
*/
|
||||
public static function geographicAnomaly(
|
||||
string $currentLocation,
|
||||
array $normalLocations,
|
||||
float $distance,
|
||||
?string $clientId = null
|
||||
): self {
|
||||
$anomalyScore = min($distance / 10000, 1.0); // Normalize by 10,000 km
|
||||
|
||||
return self::create(
|
||||
type: AnomalyType::GEOGRAPHIC_ANOMALY,
|
||||
behaviorType: BehaviorType::GEOGRAPHIC_PATTERNS,
|
||||
anomalyScore: $anomalyScore,
|
||||
description: "Geographic anomaly: access from {$currentLocation}, distance: " . round($distance) . "km from normal locations",
|
||||
evidence: [
|
||||
'current_location' => $currentLocation,
|
||||
'normal_locations' => $normalLocations,
|
||||
'distance_km' => $distance,
|
||||
]
|
||||
);
|
||||
|
||||
return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pattern deviation anomaly
|
||||
*/
|
||||
public static function patternDeviation(
|
||||
BehaviorType $behaviorType,
|
||||
string $pattern,
|
||||
float $deviationScore,
|
||||
array $features = []
|
||||
): self {
|
||||
return self::create(
|
||||
type: AnomalyType::UNUSUAL_PATTERN,
|
||||
behaviorType: $behaviorType,
|
||||
anomalyScore: $deviationScore,
|
||||
description: "Unusual pattern detected in {$behaviorType->getDescription()}: {$pattern}",
|
||||
features: $features,
|
||||
evidence: [
|
||||
'pattern' => $pattern,
|
||||
'deviation_score' => $deviationScore,
|
||||
'feature_count' => count($features),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create statistical anomaly
|
||||
*/
|
||||
public static function statisticalAnomaly(
|
||||
BehaviorType $behaviorType,
|
||||
string $metric,
|
||||
float $value,
|
||||
float $expectedValue,
|
||||
float $standardDeviation,
|
||||
?string $clientId = null
|
||||
): self {
|
||||
$zScore = $standardDeviation > 0 ? abs($value - $expectedValue) / $standardDeviation : 0;
|
||||
$anomalyScore = min($zScore / 3.0, 1.0); // Normalize by 3 sigma
|
||||
|
||||
return self::create(
|
||||
type: AnomalyType::STATISTICAL_ANOMALY,
|
||||
behaviorType: $behaviorType,
|
||||
anomalyScore: $anomalyScore,
|
||||
description: "Statistical anomaly in {$metric}: value={$value}, expected={$expectedValue}, z-score=" . round($zScore, 2),
|
||||
evidence: [
|
||||
'metric' => $metric,
|
||||
'value' => $value,
|
||||
'expected_value' => $expectedValue,
|
||||
'standard_deviation' => $standardDeviation,
|
||||
'z_score' => $zScore,
|
||||
]
|
||||
);
|
||||
|
||||
return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add client ID
|
||||
*/
|
||||
public function withClientId(string $clientId): self
|
||||
{
|
||||
return new self(
|
||||
type: $this->type,
|
||||
behaviorType: $this->behaviorType,
|
||||
confidence: $this->confidence,
|
||||
anomalyScore: $this->anomalyScore,
|
||||
description: $this->description,
|
||||
features: $this->features,
|
||||
evidence: $this->evidence,
|
||||
clientId: $clientId,
|
||||
sessionId: $this->sessionId,
|
||||
detectedAt: $this->detectedAt,
|
||||
analysisWindow: $this->analysisWindow,
|
||||
metadata: $this->metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add session ID
|
||||
*/
|
||||
public function withSessionId(string $sessionId): self
|
||||
{
|
||||
return new self(
|
||||
type: $this->type,
|
||||
behaviorType: $this->behaviorType,
|
||||
confidence: $this->confidence,
|
||||
anomalyScore: $this->anomalyScore,
|
||||
description: $this->description,
|
||||
features: $this->features,
|
||||
evidence: $this->evidence,
|
||||
clientId: $this->clientId,
|
||||
sessionId: $sessionId,
|
||||
detectedAt: $this->detectedAt,
|
||||
analysisWindow: $this->analysisWindow,
|
||||
metadata: $this->metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add analysis window
|
||||
*/
|
||||
public function withAnalysisWindow(Duration $window): self
|
||||
{
|
||||
return new self(
|
||||
type: $this->type,
|
||||
behaviorType: $this->behaviorType,
|
||||
confidence: $this->confidence,
|
||||
anomalyScore: $this->anomalyScore,
|
||||
description: $this->description,
|
||||
features: $this->features,
|
||||
evidence: $this->evidence,
|
||||
clientId: $this->clientId,
|
||||
sessionId: $this->sessionId,
|
||||
detectedAt: $this->detectedAt,
|
||||
analysisWindow: $window,
|
||||
metadata: $this->metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if anomaly requires immediate action
|
||||
*/
|
||||
public function requiresImmediateAction(): bool
|
||||
{
|
||||
return $this->type->requiresImmediateAction() &&
|
||||
$this->confidence->getValue() >= $this->type->getConfidenceThreshold() * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get risk level
|
||||
*/
|
||||
public function getRiskLevel(): string
|
||||
{
|
||||
$confidenceScore = $this->confidence->getValue() / 100.0;
|
||||
$combinedScore = ($this->anomalyScore + $confidenceScore) / 2.0;
|
||||
|
||||
return match (true) {
|
||||
$combinedScore >= 0.8 => 'critical',
|
||||
$combinedScore >= 0.6 => 'high',
|
||||
$combinedScore >= 0.4 => 'medium',
|
||||
$combinedScore >= 0.2 => 'low',
|
||||
default => 'info'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recommended action
|
||||
*/
|
||||
public function getRecommendedAction(): string
|
||||
{
|
||||
return $this->type->getRecommendedAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity score (0-100)
|
||||
*/
|
||||
public function getSeverityScore(): float
|
||||
{
|
||||
$typeWeight = match ($this->type->getSeverityLevel()) {
|
||||
'high' => 0.9,
|
||||
'medium' => 0.6,
|
||||
'low' => 0.3,
|
||||
default => 0.5
|
||||
};
|
||||
|
||||
$confidenceWeight = $this->confidence->getValue() / 100.0;
|
||||
$anomalyWeight = $this->anomalyScore;
|
||||
|
||||
return ($typeWeight * 0.4 + $confidenceWeight * 0.3 + $anomalyWeight * 0.3) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for logging/storage
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type->value,
|
||||
'behavior_type' => $this->behaviorType->value,
|
||||
'confidence' => $this->confidence->getValue(),
|
||||
'anomaly_score' => $this->anomalyScore,
|
||||
'description' => $this->description,
|
||||
'client_id' => $this->clientId,
|
||||
'session_id' => $this->sessionId,
|
||||
'detected_at' => $this->detectedAt?->format('c'),
|
||||
'analysis_window_seconds' => $this->analysisWindow?->toSeconds(),
|
||||
'features' => array_map(fn (BehaviorFeature $f) => $f->toArray(), $this->features),
|
||||
'evidence' => $this->evidence,
|
||||
'risk_level' => $this->getRiskLevel(),
|
||||
'severity_score' => $this->getSeverityScore(),
|
||||
'requires_immediate_action' => $this->requiresImmediateAction(),
|
||||
'recommended_action' => $this->getRecommendedAction(),
|
||||
'metadata' => $this->metadata,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create summary for dashboard/alerting
|
||||
*/
|
||||
public function getSummary(): array
|
||||
{
|
||||
return [
|
||||
'id' => md5($this->type->value . $this->behaviorType->value . ($this->detectedAt?->format('c') ?? '')),
|
||||
'type' => $this->type->value,
|
||||
'description' => $this->description,
|
||||
'risk_level' => $this->getRiskLevel(),
|
||||
'confidence' => $this->confidence->getValue(),
|
||||
'client_id' => $this->clientId,
|
||||
'detected_at' => $this->detectedAt?->format('c'),
|
||||
'requires_action' => $this->requiresImmediateAction(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user