From 081d06794a90ce45406ac1f12db9f843d8077327 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 5 Jul 2026 04:44:54 +0000 Subject: feat(gateway): add publicPaths bypass for all-method service routes Adds a publicPaths field to serviceMount alongside webhookPaths. webhookPaths remains POST-only (HMAC-signed webhooks). publicPaths bypasses session auth for all methods, relying on the upstream service's own bearer-token auth instead. Used to expose /claudomator/chatbot/mcp for MCP client connections (claude-code) which carry their own Authorization header and cannot carry a doot session cookie. --- cmd/dashboard/main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index 7e58125..fc26c06 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -169,7 +169,8 @@ func main() { name string upstream string prefix string - webhookPaths []string // POST paths that bypass session auth (e.g. HMAC-signed webhooks) + webhookPaths []string // POST-only paths that bypass session auth (e.g. HMAC-signed webhooks) + publicPaths []string // all-method paths that bypass session auth (protected by service-level bearer token) } mounts := []serviceMount{ { @@ -177,6 +178,7 @@ func main() { upstream: cfg.ClaudomatorURL, prefix: "/claudomator", webhookPaths: []string{"/claudomator/api/webhooks/github"}, + publicPaths: []string{"/claudomator/chatbot/mcp"}, }, } if cfg.PlaygroundURL != "" { @@ -281,6 +283,10 @@ func main() { for _, wp := range m.webhookPaths { r.Post(wp, proxy.ServeHTTP) } + // Public all-method paths bypass session auth (rely on service-level bearer token) + for _, pp := range m.publicPaths { + r.Handle(pp, http.HandlerFunc(proxy.ServeHTTP)) + } // All other routes require auth + origin check (CSRF protection for proxy) r.Group(func(r chi.Router) { -- cgit v1.2.3 From 24a87b9e979abe6c2ba23421293dc0cd8cb6d65f Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 5 Jul 2026 04:50:42 +0000 Subject: fix(proxy): set Host header to upstream target to satisfy loopback MCP DNS-rebinding check mcp.NewStreamableHTTPHandler rejects requests where the server is bound to loopback but the Host header is not (DNS-rebinding protection). Requests proxied through doot arrived at claudomator with Host: doot.terst.org, triggering a 403. Set req.Host = target.Host in the ReverseProxy Director so upstream services see the target host (e.g. 127.0.0.1:8484) rather than the original public hostname. --- internal/handlers/proxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/handlers/proxy.go b/internal/handlers/proxy.go index ce10a6a..ce328ef 100644 --- a/internal/handlers/proxy.go +++ b/internal/handlers/proxy.go @@ -31,6 +31,7 @@ func NewServiceProxy(targetURL, allowedOrigin, pathPrefix string) http.Handler { Director: func(req *http.Request) { req.URL.Scheme = target.Scheme req.URL.Host = target.Host + req.Host = target.Host // use upstream host so loopback services see a loopback Host header req.URL.Path = stripPrefix(req.URL.Path) if req.URL.RawPath != "" { req.URL.RawPath = stripPrefix(req.URL.RawPath) -- cgit v1.2.3