gastownhall/beads · error

--allowed-host %q looks like a URL; pass just the host, with

Error message

--allowed-host %q looks like a URL; pass just the host, with no scheme and no path

What it means

ValidateAllowedHost rejects values that look like URLs (containing '/' or '@'). The flag expects only the host name; a scheme or path would never match the Host header and indicates the operator pasted a full URL.

Source

Thrown at internal/httpapi/server.go:1865

	}
	return p
}

// ValidateAllowedHost refuses an allowlist entry that is not a bare host.
//
// The Host header's port is stripped before matching (hostOnly), so an entry
// carrying one would silently never match — and an operator who wrote it would
// reasonably read the startup line as proof that it does. A URL, a path or
// embedded whitespace is the same mistake in a louder form.
func ValidateAllowedHost(v string) error {
	if strings.TrimSpace(v) == "" {
		return errors.New("--allowed-host is empty; pass the Host header value clients send, such as bd-myproject.beads.svc.cluster.local")
	}
	if strings.ContainsAny(v, " \t\r\n") {
		return fmt.Errorf("--allowed-host %q contains whitespace; it must be a bare host name or IP", v)
	}
	if strings.ContainsAny(v, "/@") {
		return fmt.Errorf("--allowed-host %q looks like a URL; pass just the host, with no scheme and no path", v)
	}
	// An IPv6 address is spelled in brackets in a Host header, so an operator
	// copying one off the wire types it that way. hostOnly strips them before
	// matching, so the entry works; refusing it here — with a message about a
	// port it does not have — would be the validation lying about the policy.
	if net.ParseIP(strings.TrimSuffix(strings.TrimPrefix(v, "["), "]")) != nil {
		return nil
	}
	if strings.Contains(v, ":") {
		return fmt.Errorf("--allowed-host %q carries a port; the port is stripped from a request's Host before matching, so an entry with one could never match", v)
	}
	return nil
}

// allows reports whether a Host header value is one this server answers to.
func (p hostPolicy) allows(host string) bool {
	h := hostOnly(host)
	if p.names[h] {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Strip the scheme and path, keeping only the host: bd.example.com instead of https://bd.example.com/
  2. For IPv6, keep bracket form like [::1] (hostOnly strips brackets before matching)
  3. Keep the URL in the client's config but use only its hostname portion for --allowed-host

Example fix

// before
--allowed-host https://bd.example.com/api
// after
--allowed-host bd.example.com
Defensive patterns

Strategy: validation

Validate before calling

if strings.ContainsAny(v, "/@") {
    u, err := url.Parse(v)
    if err == nil && u.Hostname() != "" { v = u.Hostname() } // reduce URL to host
}

Try / catch

if err := httpapi.ValidateAllowedHost(v); err != nil {
    log.Fatalf("invalid --allowed-host: %v", err)
}

Prevention

When it happens

Trigger: Calling ValidateAllowedHost with "https://bd.example.com", "bd.example.com/api", or values containing '@' such as "user@bd.example.com".

Common situations: Copying the server's full URL from a browser or client config into --allowed-host; confusing the client's base-URL setting with the server's host allow-list.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d315325b39604453. Report an issue: GitHub.