diff --git a/internal/web/service/integration/warp.go b/internal/web/service/integration/warp.go index 3f6c98997..14598b42e 100644 --- a/internal/web/service/integration/warp.go +++ b/internal/web/service/integration/warp.go @@ -238,7 +238,7 @@ func (s *WarpService) doWarpRequest(req *http.Request) ([]byte, error) { } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) if err != nil { return nil, err } diff --git a/internal/web/service/integration/warp_response_test.go b/internal/web/service/integration/warp_response_test.go new file mode 100644 index 000000000..d37ca1f9b --- /dev/null +++ b/internal/web/service/integration/warp_response_test.go @@ -0,0 +1,42 @@ +package integration + +import ( + "bytes" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "github.com/mhsanaei/3x-ui/v3/internal/database" +) + +// A hostile egress proxy (or a MITM on the WARP endpoint) could stream an +// arbitrarily large body; doWarpRequest must cap the read at maxResponseSize so +// the panel cannot be forced into an unbounded allocation. +func TestDoWarpRequestCapsResponseBody(t *testing.T) { + if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil { + t.Fatalf("InitDB: %v", err) + } + t.Cleanup(func() { _ = database.CloseDB() }) + + oversize := maxResponseSize + 4096 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(bytes.Repeat([]byte("a"), oversize)) + })) + defer srv.Close() + + s := &WarpService{} + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + + body, err := s.doWarpRequest(req) + if err != nil { + t.Fatalf("doWarpRequest: %v", err) + } + if len(body) != maxResponseSize { + t.Fatalf("response body not capped: got %d bytes, want %d", len(body), maxResponseSize) + } +}