netbirdio/netbird · warning

unauthorized

Error message

unauthorized

What it means

HTTP 401 from isValidRequest when the request lacks the x-nb-client header or its value is not exactly 'netbird' (types.ClientHeader / types.ClientHeaderValue). This is a light client-identification gate, not authentication: the header value must simply match exactly.

Source

Thrown at upload-server/server/server.go:86

func getObjectKey(w http.ResponseWriter, r *http.Request) string {
	id := r.URL.Query().Get("id")
	if id == "" {
		http.Error(w, "id query param required", http.StatusBadRequest)
		return ""
	}

	return id + "/" + uuid.New().String()
}

func isValidRequest(w http.ResponseWriter, r *http.Request) bool {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return false
	}

	if r.Header.Get(types.ClientHeader) != types.ClientHeaderValue {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return false
	}
	return true
}
func respondGetRequest(w http.ResponseWriter, uploadURL string, objectKey string) {
	response := types.GetURLResponse{
		URL: uploadURL,
		Key: objectKey,
	}

	rdata, err := json.Marshal(response)
	if err != nil {
		http.Error(w, "failed to marshal response", http.StatusInternalServerError)
		log.Errorf("Marshal error: %v", err)
		return
	}

	w.WriteHeader(http.StatusOK)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Add the header: x-nb-client: netbird (exact value)
  2. If a proxy is in the path, verify it forwards custom request headers
  3. Remember this is identification, not auth: do not expose the endpoint publicly without a real auth layer in front

Example fix

# before -> 401 unauthorized
curl 'https://srv/upload-url?id=x'

# after
curl 'https://srv/upload-url?id=x' -H 'x-nb-client: netbird'
Defensive patterns

Strategy: validation

Validate before calling

req.Header.Set("x-nb-client", "netbird")
// or, shared with server code:
req.Header.Set(types.ClientHeader, types.ClientHeaderValue)

Try / catch

On 401 'unauthorized', verify the x-nb-client header is present with the exact value 'netbird' (no version suffix, no whitespace), then retry once; a persistent 401 means a proxy is stripping the header.

Prevention

When it happens

Trigger: GET /upload-url without the header; header present but value mismatched ('NetBird', 'netbird/1.0', trailing whitespace); a proxy or CORS layer stripping custom x-nb-* headers.

Common situations: curl/fetch test calls that forget the header; intermediaries normalizing or dropping unknown headers; header name case is fine (HTTP headers are case-insensitive) but the value comparison is exact.

Understand the failure class

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/af53741468ee873d. Report an issue: GitHub.