From ed15ef8c23a45251897c533bc904e65bc1c9cace Mon Sep 17 00:00:00 2001 From: "Fabian @ Blax Software" Date: Tue, 4 Aug 2026 10:21:44 +0200 Subject: [PATCH] fix(auth): re-establish auth guard in fork child; suppress EPIPE on peer-gone write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deferred-message fork path (processDeferredMessages -> forkWithSocketPair) never re-runs authenticateConnection, so the child inherited $connection->user (passing the need_auth gate) but a cleared Auth guard — auth()->user() / User::auth() returned null, crashing handlers that read the guard (learn-atc GlitchTip #529/#532/#535/#531/#530). Sync the guard from the mock's user per fork with Auth::setUser() (setUser, not login, to avoid re-firing the Login event on every message). Also guard SocketPairIpc::sendToParent with @socket_write: when the WS client disconnects mid-response the parent tears down its read end, so the child's next write races a closed pipe (EPIPE / "Broken pipe"). That benign warning was promoted to a reported ErrorException and flooded GlitchTip (#461). Mirrors the existing @fwrite in Broadcast/BroadcastClient.php. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Ipc/SocketPairIpc.php | 8 +++++++- src/Websocket/Handler.php | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Ipc/SocketPairIpc.php b/src/Ipc/SocketPairIpc.php index ccee6ef..df33e9e 100644 --- a/src/Ipc/SocketPairIpc.php +++ b/src/Ipc/SocketPairIpc.php @@ -151,7 +151,13 @@ class SocketPairIpc // Newline-delimited framing $message = $data . "\n"; - $written = socket_write($this->sockets[1], $message, strlen($message)); + // Suppress the write warning: when the WS client disconnects mid-response + // the parent tears down its read end, so the child's next write races a + // closed pipe (EPIPE / "Broken pipe"). That warning is otherwise promoted + // to a reported ErrorException and floods GlitchTip for a benign, expected + // disconnect. The false-return is still handled by the caller. Mirrors the + // existing @fwrite in Broadcast/BroadcastClient.php. + $written = @socket_write($this->sockets[1], $message, strlen($message)); return $written === strlen($message); } diff --git a/src/Websocket/Handler.php b/src/Websocket/Handler.php index f24253d..f6bb7aa 100644 --- a/src/Websocket/Handler.php +++ b/src/Websocket/Handler.php @@ -619,6 +619,29 @@ class Handler implements MessageComponentInterface // Create mock that sends via socket pair $mock = new MockConnectionSocketPair($connection, $ipc); + // Re-establish this fork's Laravel auth guard from the + // connection's user. authenticateConnection() logs the guard in, + // but it runs in the PARENT before forking — deferred messages + // fork later via processDeferredMessages() WITHOUT re-authenticating, + // and the parent's guard may since have been cleared (scheduleLogout) + // or reassigned to another connection. The mock still proxies the + // correct $connection->user (so the need_auth gate passes), yet + // auth()->user() / User::auth() would be null in this child — + // crashing any handler that reads the guard instead of ->user. + // Sync the guard to the connection here so every handler, deferred + // or not, sees a consistent authenticated user. setUser() (not + // login()) avoids re-firing the Login event on every message. + try { + if ($mock->user) { + Auth::setUser($mock->user); + } elseif (Auth::hasUser()) { + Auth::logout(); + } + } catch (\Throwable $authSyncError) { + // Best-effort: the need_auth gate in controll_message() is + // still the hard authorization check. + } + $this->executeControllerWithDbResilience( $mock, $channel,