netbirdio/netbird · warning

invalid path

Error message

invalid path

What it means

HTTP 400 returned by the local-storage upload handler (PUT /upload/{dir}/{file}) when the {dir} path segment, after filepath.Join with the base directory and filepath.Clean, no longer has the cleaned base (STORE_DIR, default /var/lib/netbird, plus a trailing separator) as a prefix. It is a deliberate path-traversal guard: the server logs 'Path traversal attempt blocked (dir)' and refuses the request before creating anything.

Source

Thrown at upload-server/server/local.go:116

		return
	}

	uploadDir := r.PathValue("dir")
	if uploadDir == "" {
		http.Error(w, "missing dir path", http.StatusBadRequest)
		return
	}
	uploadFile := r.PathValue("file")
	if uploadFile == "" {
		http.Error(w, "missing file name", http.StatusBadRequest)
		return
	}

	cleanBase := filepath.Clean(l.dir) + string(filepath.Separator)

	dirPath := filepath.Clean(filepath.Join(l.dir, uploadDir))
	if !strings.HasPrefix(dirPath, cleanBase) {
		http.Error(w, "invalid path", http.StatusBadRequest)
		log.Warnf("Path traversal attempt blocked (dir): %s", dirPath)
		return
	}

	filePath := filepath.Clean(filepath.Join(dirPath, uploadFile))
	if !strings.HasPrefix(filePath, cleanBase) {
		http.Error(w, "invalid path", http.StatusBadRequest)
		log.Warnf("Path traversal attempt blocked (file): %s", filePath)
		return
	}

	if err = os.MkdirAll(dirPath, 0750); err != nil {
		http.Error(w, "failed to create upload dir", http.StatusInternalServerError)
		log.Errorf("Failed to create upload dir: %v", err)
		return
	}

	flags := os.O_WRONLY | os.O_CREATE | os.O_EXCL

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Use the PUT URL returned by GET /upload-url verbatim; it already embeds a fresh <id>/<uuid> key under the base dir
  2. If building the path manually, keep {dir} a single safe segment: no '..', '/', backslash, NUL, or absolute path, and percent-encode it with url.PathEscape
  3. If you operate the server, keep STORE_DIR absolute (startup enforces this) and treat repeated 'Path traversal attempt blocked' warns as probing (rate-limit the source)

Example fix

// before (rejected with 400 invalid path)
PUT /upload/..%2F..%2Fetc/cron.d/pwn

// after: fetch an upload URL and use it unchanged
curl -H 'x-nb-client: netbird' 'https://srv/upload-url?id=peer123'
// -> {"url":"https://srv/upload/peer123/<uuid>","key":"peer123/<uuid>"}
curl -X PUT --data-binary @file 'https://srv/upload/peer123/<uuid>'
Defensive patterns

Strategy: validation

Validate before calling

func safeSegment(s string) bool {
	return s != "" && s != "." && s != ".." &&
		!strings.Contains(s, "/") && !strings.Contains(s, "\\") && s == path.Base(path.Clean(s))
}

// before issuing the PUT
if !safeSegment(dir) || !safeSegment(file) {
	return fmt.Errorf("unsafe upload path segment")
}

Try / catch

After the PUT, if resp.StatusCode == 400 and the body reads 'invalid path', fix the client's path construction; do not blind-retry the same URL, it will fail identically.

Prevention

When it happens

Trigger: PUT to /upload/{dir}/{file} where dir contains dot-dot segments or encoded separators that ServeMux decodes into PathValue, e.g. PUT /upload/..%2F..%2Fetc/cron.d/x, or a dir value that Join+Clean resolves outside the base directory.

Common situations: A client hand-crafting the PUT URL instead of using the one returned by GET /upload-url; a proxy that decodes %2E%2E or %2F before the request reaches Go's ServeMux; automated scanners probing for traversal.

Related errors


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