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
- Add the header: x-nb-client: netbird (exact value)
- If a proxy is in the path, verify it forwards custom request headers
- 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
- Centralize header injection in one HTTP client wrapper so no call site forgets it
- Check that corporate proxies/CORS configurations forward custom x-nb-* headers
- Do not treat this header as security; put real auth in front of public deployments
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- invalid path
- file already exists
- id query param required
- service domain is required
- listen_port is not supported for HTTP services
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/af53741468ee873d.
Report an issue: GitHub.