initializeSecurityLayers(); } /** * Initialize security layers if not already registered */ private function initializeSecurityLayers(): void { // Check if layers are already registered (avoid double registration) $healthStatus = $this->wafEngine->getHealthStatus(); if ($healthStatus['total_layers'] > 0) { return; // Already initialized } try { // Register security layers directly $this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\SqlInjectionLayer()); $this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\CommandInjectionLayer()); $this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\PathTraversalLayer()); $this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\XssLayer()); $this->wafEngine->registerLayer(new \App\Framework\Waf\Layers\SuspiciousUserAgentLayer()); $this->logger->info('WAF security layers initialized', LogContext::withData([ 'layers_count' => 5, 'health_status' => $this->wafEngine->getHealthStatus(), ])); } catch (\Throwable $e) { $this->logger->error('Failed to initialize WAF security layers', LogContext::withData([ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ])); } } public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { if (! $this->config->enabled) { $this->logger->debug('WAF disabled, skipping analysis'); return $next($context); } try { $request = $context->request; // Debug log request details $this->logger->debug('WAF analyzing request', LogContext::withData([ 'path' => $request->path ?? '/', 'method' => $request->method->value ?? 'UNKNOWN', 'query_params' => $request->queryParams, 'post_data' => $request->parsedBody->data ?? null, 'user_agent' => $request->headers->get('User-Agent', ''), 'client_ip' => $request->server->getClientIp()?->value ?? 'unknown', 'waf_config' => [ 'enabled' => $this->config->enabled, 'blocking_mode' => $this->config->blockingMode, 'enabled_layers' => $this->config->enabledLayers, ], ])); // Analyze request with WAF engine $wafResult = $this->wafEngine->analyze($request); // Debug log analysis result $this->logger->debug('WAF analysis complete', LogContext::withData([ 'result_status' => $wafResult->getStatus()->value ?? 'unknown', 'result_action' => $wafResult->getAction(), 'layer_name' => $wafResult->getLayerName(), 'message' => $wafResult->getMessage(), 'has_detections' => $wafResult->hasDetections(), 'detections_count' => $wafResult->hasDetections() ? count($wafResult->getDetections()->getAll()) : 0, ])); // Handle based on result action switch ($wafResult->getAction()) { case LayerResult::ACTION_BLOCK: $this->logger->info('WAF blocking request', LogContext::withData(['reason' => $wafResult->getMessage()])); return $this->handleBlocked($context, $wafResult); case LayerResult::ACTION_SUSPICIOUS: $this->logger->info('WAF flagging suspicious request', LogContext::withData(['reason' => $wafResult->getMessage()])); return $this->handleSuspicious($context, $wafResult, $next); case LayerResult::ACTION_PASS: default: $this->logger->debug('WAF allowing request', LogContext::withData(['reason' => $wafResult->getMessage()])); // Continue to next middleware return $next($context); } } catch (\Throwable $e) { // Log WAF error but don't block request on WAF failure $this->logger->error('WAF middleware error', LogContext::withData([ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'request_path' => $context->request->path ?? '/', 'client_ip' => $context->request->server->getClientIp()?->value ?? 'unknown', ])); // Continue processing on WAF error return $next($context); } } /** * Handle blocked requests */ private function handleBlocked(MiddlewareContext $context, LayerResult $wafResult): MiddlewareContext { $request = $context->request; $clientIp = $request->server->getClientIp()?->value ?? 'unknown'; // Log security event $this->logger->warning('WAF blocked request', LogContext::withData([ 'reason' => $wafResult->getMessage(), 'client_ip' => $clientIp, 'path' => $request->path ?? '/', 'user_agent' => $request->headers->get('User-Agent', ''), 'detections' => $this->formatDetections($wafResult), 'layer' => $wafResult->getLayerName(), ])); if ($this->config->blockingMode) { // Return 403 Forbidden response $response = new JsonResponse([ 'error' => 'Request blocked by security policy', 'code' => 'WAF_BLOCKED', 'request_id' => $request->id->value(), ], 403); } else { // Log-only mode - continue processing but log the threat $this->logger->warning('WAF would block request (log-only mode)', LogContext::withData([ 'reason' => $wafResult->getMessage(), 'client_ip' => $clientIp, 'path' => $request->path ?? '/', ])); $response = new JsonResponse([ 'warning' => 'Request flagged by security policy', 'code' => 'WAF_FLAGGED', ], 200); } return $context->withResponse($response); } /** * Handle suspicious requests */ private function handleSuspicious(MiddlewareContext $context, LayerResult $wafResult, Next $next): MiddlewareContext { $request = $context->request; $clientIp = $request->server->getClientIp()?->value ?? 'unknown'; // Log suspicious activity $this->logger->info('WAF flagged suspicious request', LogContext::withData([ 'reason' => $wafResult->getMessage(), 'client_ip' => $clientIp, 'path' => $request->path ?? '/', 'user_agent' => $request->headers->get('User-Agent', ''), 'detections' => $this->formatDetections($wafResult), 'layer' => $wafResult->getLayerName(), ])); // Continue to next middleware $resultContext = $next($context); // Add WAF detection info to response headers for monitoring if ($resultContext->hasResponse()) { $response = $resultContext->response; $updatedHeaders = $response->headers ->with('X-Waf-Status', 'suspicious') ->with('X-Waf-Layer', $wafResult->getLayerName()); $updatedResponse = new HttpResponse( $response->body, $response->statusCode, $updatedHeaders ); return $resultContext->withResponse($updatedResponse); } return $resultContext; } /** * Format detection information for logging */ private function formatDetections(LayerResult $wafResult): array { if (! $wafResult->hasDetections()) { return []; } $detections = []; foreach ($wafResult->getDetections()->getAll() as $detection) { $detections[] = [ 'category' => $detection->category->value, 'severity' => $detection->severity->value, 'message' => $detection->message, 'confidence' => $detection->confidence, 'evidence' => $detection->evidence, ]; } return $detections; } }