check()) { return $next($request); } $canaries = config('clusev.honeypot.canaries', []); if ($canaries === []) { return $next($request); } // A small, bounded set of candidate values — request BODY only (post()), no query string, // no recursion into nested arrays. $candidates = []; foreach (self::CANDIDATE_KEYS as $key) { $value = $request->post($key); if (is_string($value) && $value !== '') { $candidates[] = $value; } } if (($bearer = $request->bearerToken()) !== null && $bearer !== '') { $candidates[] = $bearer; } if (($auth = $request->header('Authorization')) !== null && $auth !== '') { $candidates[] = $auth; } foreach ($canaries as $name => $canaryValue) { if (! is_string($canaryValue) || $canaryValue === '') { continue; } foreach ($candidates as $candidate) { if (hash_equals($canaryValue, $candidate)) { return $this->trip($request, (string) $name); } } } return $next($request); } private function trip(Request $request, string $canaryName): Response { $ip = (string) $request->ip(); $this->guard->banNow($ip, 'honeytoken'); AuditEvent::create([ 'user_id' => null, 'actor' => 'system', 'action' => 'security.honeytoken_used', 'target' => $canaryName, 'ip' => $ip, 'meta' => ['path' => $request->path()], ]); return response()->view('errors.blocked', [], 403); } }