Files
michaelschiemer/src/Framework/Waf/MachineLearning/ValueObjects/AnomalyDetection.php
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

340 lines
11 KiB
PHP

<?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(),
];
}
}