Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

74 lines
2.3 KiB
Go

package server
import (
"net"
"net/http"
"strings"
)
// Route-scoped DNS-rebinding guard for the conversation-log routes.
//
// The conversation routes egress raw LLM request/response text, so they
// carry a tighter check than the rest of the HTTP surface. The guard is
// deliberately NOT a global Host-allowlist layer in ServeHTTP — adding
// one there would 403 a legitimately token-authed non-loopback dashboard
// deployment. Instead it is applied only inside the conversation
// handlers and COOPERATES with the existing --http-auth-token model: a
// request is allowed when EITHER its Host is loopback / explicitly
// allowlisted, OR a valid auth token was presented. An un-authed
// cross-origin / DNS-rebind request to a conversation route is the only
// thing rejected.
// guardConversationRoute reports whether a request to a /v1/conversations*
// route is allowed. It passes when the Host is loopback or in the
// allowlist, or when a valid auth token was presented (tokenOK). Only an
// un-authed, non-loopback, non-allowlisted request is rejected.
func guardConversationRoute(r *http.Request, allow []string, tokenOK bool) bool {
if tokenOK {
return true
}
return hostAllowed(r.Host, allow)
}
// hostAllowed reports whether host (a request's Host header, possibly
// with a port) is a loopback address/name or appears in the extra
// allowlist. An empty Host is treated as not allowed.
func hostAllowed(host string, extra []string) bool {
host = strings.TrimSpace(host)
if host == "" {
return false
}
hostname := host
if h, _, err := net.SplitHostPort(host); err == nil {
hostname = h
}
hostname = strings.TrimSuffix(strings.TrimPrefix(hostname, "["), "]")
if isLoopbackHost(hostname) {
return true
}
for _, a := range extra {
a = strings.TrimSpace(a)
if a == "" {
continue
}
// Compare against the bare hostname and the host[:port] as given,
// so an allowlist entry may name either form.
if strings.EqualFold(a, hostname) || strings.EqualFold(a, host) {
return true
}
}
return false
}
// isLoopbackHost reports whether a bare hostname is the loopback name or
// a loopback IP literal (127.0.0.0/8, ::1).
func isLoopbackHost(hostname string) bool {
if strings.EqualFold(hostname, "localhost") {
return true
}
if ip := net.ParseIP(hostname); ip != nil {
return ip.IsLoopback()
}
return false
}