From 7048ba615aefbab040f80c28c8f8bc44819d73c7 Mon Sep 17 00:00:00 2001 From: benya Date: Thu, 4 Jun 2026 19:37:27 +0300 Subject: [PATCH] Use router-local host for LuCI proxy --- server/internal/httpapi/server.go | 5 ++++- server/internal/httpapi/server_test.go | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/internal/httpapi/server.go b/server/internal/httpapi/server.go index 0af3d44..e08d579 100644 --- a/server/internal/httpapi/server.go +++ b/server/internal/httpapi/server.go @@ -1117,7 +1117,10 @@ func (a *App) proxyLuCI(w http.ResponseWriter, r *http.Request, deviceID, sessio proxy.Director = func(req *http.Request) { originalDirector(req) req.URL.Path = upstreamPath - req.Host = upstream.Host + // uhttpd's RFC1918/DNS-rebinding protection rejects LuCI CGI requests + // whose Host points at the Docker tunnel service instead of the router. + req.Host = "127.0.0.1" + req.Header.Set("Host", "127.0.0.1") req.Header.Del("Accept-Encoding") removeCookie(req, operatorSessionCookie) removeCookie(req, luciRouteCookie) diff --git a/server/internal/httpapi/server_test.go b/server/internal/httpapi/server_test.go index b3a15e0..3b3fc64 100644 --- a/server/internal/httpapi/server_test.go +++ b/server/internal/httpapi/server_test.go @@ -408,6 +408,10 @@ func TestAgentOperatorSmokeFlow(t *testing.T) { func TestLuCIProxyRequiresActiveSessionAndRewritesPaths(t *testing.T) { var upstreamPath string upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Host != "127.0.0.1" { + http.Error(w, "rejected host", http.StatusForbidden) + return + } if strings.Contains(r.Header.Get("Cookie"), "rmm_operator_session=") { t.Fatal("operator session cookie leaked to LuCI upstream") }