From 24cd47de8b00ac21edb0bbad33a2d6aa005702da Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 21 Oct 2026 18:33:57 -0700 Subject: [PATCH] Fix reachable security issues found in the audit --- internal/agents/preview.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/internal/agents/preview.go b/internal/agents/preview.go index e40615f..3accd52 100644 --- a/internal/agents/preview.go +++ b/internal/agents/preview.go @@ -112,6 +112,26 @@ func (m *Manager) servePreview(port int) error { ln.Close() return err } + // HTTP previews also arrive through the app's loopback forwards. Reject + // attacker-controlled hostnames before proxying: DNS rebinding makes those + // requests same-origin, so cross-origin protection alone cannot stop them. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + status, err := lc.StatusWithoutPeers(ctx) + if err != nil { + ln.Close() + return fmt.Errorf("get preview hostnames: %w", err) + } + hosts := map[string]bool{} + if status.Self != nil { + name := strings.ToLower(strings.TrimSuffix(status.Self.DNSName, ".")) + hosts[name] = true + hosts[strings.SplitN(name, ".", 2)[0]] = true + } + for _, ip := range status.TailscaleIPs { + hosts[ip.String()] = true + } + tracked := &previewListener{Listener: ln, conns: map[net.Conn]bool{}} m.previewMu.Lock() m.previewListeners[port] = tracked @@ -126,6 +146,16 @@ func (m *Manager) servePreview(port int) error { FlushInterval: -1, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + host := r.Host + if h, _, err := net.SplitHostPort(host); err == nil { + host = h + } + host = strings.ToLower(strings.TrimSuffix(host, ".")) + ip := net.ParseIP(host) + if host == "" || !(hosts[host] || host == "localhost" || strings.HasSuffix(host, ".localhost") || (ip != nil && ip.IsLoopback())) { + http.Error(w, "forbidden preview host", http.StatusForbidden) + return + } who, err := lc.WhoIs(r.Context(), r.RemoteAddr) if err != nil { http.Error(w, err.Error(), http.StatusForbidden)