From 89c27c5835dfa485e5ce44a2c132cfc36f7d0540 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 03:21:11 +0200 Subject: [PATCH] fix(xray): guard RemoveUser against an uninitialized handler client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every XrayAPI handler method returns an error when HandlerServiceClient is nil, except RemoveUser, which dereferenced it directly. A depletion sweep runs Init with the port ignored and, during a restart window where the fresh process's api port is still 0, Init fails and leaves the client nil — so RemoveUser panicked (recovered by the traffic writer, but re-thrown every poll) instead of returning an error. Add the same nil guard the siblings have. --- internal/xray/api.go | 7 ++++++- internal/xray/api_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/xray/api.go b/internal/xray/api.go index b9ffc78aa..29332212c 100644 --- a/internal/xray/api.go +++ b/internal/xray/api.go @@ -637,6 +637,11 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an // RemoveUser removes a user from an inbound in the Xray core by email. func (x *XrayAPI) RemoveUser(inboundTag, email string) error { + if x.HandlerServiceClient == nil { + return common.NewError("xray HandlerServiceClient is not initialized") + } + client := *x.HandlerServiceClient + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -646,7 +651,7 @@ func (x *XrayAPI) RemoveUser(inboundTag, email string) error { Operation: serial.ToTypedMessage(op), } - _, err := (*x.HandlerServiceClient).AlterInbound(ctx, req) + _, err := client.AlterInbound(ctx, req) if err != nil { return fmt.Errorf("failed to remove user: %w", err) } diff --git a/internal/xray/api_test.go b/internal/xray/api_test.go index 3f018f524..c3ceefe2a 100644 --- a/internal/xray/api_test.go +++ b/internal/xray/api_test.go @@ -5,6 +5,16 @@ import ( "testing" ) +// RemoveUser must return an error, not panic, when the handler client is not +// initialized — matching every sibling API method. A depletion sweep can reach +// it with a nil client during a restart window where Init(0) failed. +func TestRemoveUserGuardsNilHandlerClient(t *testing.T) { + err := (&XrayAPI{}).RemoveUser("in-443-tcp", "user@example.com") + if err == nil { + t.Fatal("RemoveUser with an uninitialized HandlerServiceClient must return an error") + } +} + func TestGetRequiredUserString_Present(t *testing.T) { user := map[string]any{"email": "alice@example.com"} got, err := getRequiredUserString(user, "email")