{"record":{"id":"fe170fa0289e4914","repo":"netbirdio/netbird","slug":"invalid-path","errorCode":null,"errorMessage":"invalid path","messagePattern":"invalid path","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"upload-server/server/local.go","lineNumber":116,"sourceCode":"\t\treturn\n\t}\n\n\tuploadDir := r.PathValue(\"dir\")\n\tif uploadDir == \"\" {\n\t\thttp.Error(w, \"missing dir path\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tuploadFile := r.PathValue(\"file\")\n\tif uploadFile == \"\" {\n\t\thttp.Error(w, \"missing file name\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tcleanBase := filepath.Clean(l.dir) + string(filepath.Separator)\n\n\tdirPath := filepath.Clean(filepath.Join(l.dir, uploadDir))\n\tif !strings.HasPrefix(dirPath, cleanBase) {\n\t\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\t\tlog.Warnf(\"Path traversal attempt blocked (dir): %s\", dirPath)\n\t\treturn\n\t}\n\n\tfilePath := filepath.Clean(filepath.Join(dirPath, uploadFile))\n\tif !strings.HasPrefix(filePath, cleanBase) {\n\t\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\t\tlog.Warnf(\"Path traversal attempt blocked (file): %s\", filePath)\n\t\treturn\n\t}\n\n\tif err = os.MkdirAll(dirPath, 0750); err != nil {\n\t\thttp.Error(w, \"failed to create upload dir\", http.StatusInternalServerError)\n\t\tlog.Errorf(\"Failed to create upload dir: %v\", err)\n\t\treturn\n\t}\n\n\tflags := os.O_WRONLY | os.O_CREATE | os.O_EXCL","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/upload-server/server/local.go#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use the PUT URL returned by GET /upload-url verbatim; it already embeds a fresh <id>/<uuid> key under the base dir","If building the path manually, keep {dir} a single safe segment: no '..', '/', backslash, NUL, or absolute path, and percent-encode it with url.PathEscape","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)"],"exampleFix":"// before (rejected with 400 invalid path)\nPUT /upload/..%2F..%2Fetc/cron.d/pwn\n\n// after: fetch an upload URL and use it unchanged\ncurl -H 'x-nb-client: netbird' 'https://srv/upload-url?id=peer123'\n// -> {\"url\":\"https://srv/upload/peer123/<uuid>\",\"key\":\"peer123/<uuid>\"}\ncurl -X PUT --data-binary @file 'https://srv/upload/peer123/<uuid>'","handlingStrategy":"validation","validationCode":"func safeSegment(s string) bool {\n\treturn s != \"\" && s != \".\" && s != \"..\" &&\n\t\t!strings.Contains(s, \"/\") && !strings.Contains(s, \"\\\\\") && s == path.Base(path.Clean(s))\n}\n\n// before issuing the PUT\nif !safeSegment(dir) || !safeSegment(file) {\n\treturn fmt.Errorf(\"unsafe upload path segment\")\n}","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["Never hand-assemble the PUT path; always use the URL returned by GET /upload-url","Reject any segment containing '/', backslash, '..', NUL, or absolute paths before sending","Percent-encode path values with url.PathEscape so intermediaries cannot reinterpret them"],"tags":["security","path-traversal","http","upload","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}