From bd294982b219366ac6adfe1eb587ae854cc24fdb Mon Sep 17 00:00:00 2001 From: Catherine Date: Sat, 20 Sep 2025 07:39:14 +0000 Subject: [PATCH] Disallow `Host:` values starting with a dot. Although these should never appear in first place, allowing them to proceed into application logic may cause conflicts with reserved manifest names. --- src/auth.go | 32 ++++++++++++++++++++++++-------- src/pages.go | 20 ++++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/auth.go b/src/auth.go index 46bfc31..d37a872 100644 --- a/src/auth.go +++ b/src/auth.go @@ -35,14 +35,18 @@ func InsecureMode() bool { return os.Getenv("INSECURE") == "very" } -func GetHost(r *http.Request) string { +func GetHost(r *http.Request) (string, error) { // FIXME: handle IDNA host, _, err := net.SplitHostPort(r.Host) if err != nil { // dirty but the go stdlib doesn't have a "split port if present" function host = r.Host } - return host + if strings.HasPrefix(host, ".") { + return "", AuthError{http.StatusBadRequest, + fmt.Sprintf("host name %q is reserved", host)} + } + return host, nil } func GetProjectName(r *http.Request) (string, error) { @@ -70,7 +74,10 @@ type Authorization struct { } func authorizeDNSChallenge(r *http.Request) (*Authorization, error) { - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return nil, err + } authorization := r.Header.Get("Authorization") if authorization == "" { @@ -133,7 +140,10 @@ func authorizeDNSChallenge(r *http.Request) (*Authorization, error) { } func authorizeDNSAllowlist(r *http.Request) (*Authorization, error) { - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return nil, err + } allowlistHostname := fmt.Sprintf("_git-pages-repository.%s", host) repoURLs, err := net.LookupTXT(allowlistHostname) @@ -156,9 +166,12 @@ func authorizeDNSAllowlist(r *http.Request) (*Authorization, error) { } func authorizeWildcardMatchHost(r *http.Request) (*Authorization, error) { - host := GetHost(r) - hostParts := strings.Split(host, ".") + host, err := GetHost(r) + if err != nil { + return nil, err + } + hostParts := strings.Split(host, ".") if slices.Equal(hostParts[1:], wildcardPattern.Domain) { return &Authorization{}, nil } else { @@ -170,14 +183,17 @@ func authorizeWildcardMatchHost(r *http.Request) (*Authorization, error) { } func authorizeWildcardMatchSite(r *http.Request) (*Authorization, error) { - host := GetHost(r) - hostParts := strings.Split(host, ".") + host, err := GetHost(r) + if err != nil { + return nil, err + } projectName, err := GetProjectName(r) if err != nil { return nil, err } + hostParts := strings.Split(host, ".") if slices.Equal(hostParts[1:], wildcardPattern.Domain) { userName := hostParts[0] var repoURLs []string diff --git a/src/pages.go b/src/pages.go index 3d3ee8b..edabe3b 100644 --- a/src/pages.go +++ b/src/pages.go @@ -27,7 +27,10 @@ func getPage(w http.ResponseWriter, r *http.Request) error { var sitePath string var manifest *Manifest - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return err + } // allow JavaScript code to access responses (including errors) even across origins w.Header().Set("Access-Control-Allow-Origin", "*") @@ -163,7 +166,10 @@ const SiteSizeMax = 512 * 1048576 func putPage(w http.ResponseWriter, r *http.Request) error { var result UpdateResult - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return err + } projectName, err := GetProjectName(r) if err != nil { @@ -252,7 +258,10 @@ func deletePage(w http.ResponseWriter, r *http.Request) error { return err } - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return err + } projectName, err := GetProjectName(r) if err != nil { @@ -277,7 +286,10 @@ func postPage(w http.ResponseWriter, r *http.Request) error { return err } - host := GetHost(r) + host, err := GetHost(r) + if err != nil { + return err + } projectName, err := GetProjectName(r) if err != nil {