{"record":{"id":"3a79f88efb067f30","repo":"IceWhaleTech/CasaOS","slug":"access-using-relative-path-is-not-allowed","errorCode":null,"errorMessage":"access using relative path is not allowed","messagePattern":"access using relative path is not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/utils/path.go","lineNumber":78,"sourceCode":"\t\t{\"%\", \"%25\"},\n\t\t{\"?\", \"%3F\"},\n\t\t{\"#\", \"%23\"},\n\t}\n\tfor i := range seg {\n\t\tif len(all) > 0 && all[0] {\n\t\t\tseg[i] = url.PathEscape(seg[i])\n\t\t} else {\n\t\t\tfor j := range toReplace {\n\t\t\t\tseg[i] = strings.ReplaceAll(seg[i], toReplace[j].Src, toReplace[j].Dst)\n\t\t\t}\n\t\t}\n\t}\n\treturn strings.Join(seg, \"/\")\n}\n\nfunc JoinBasePath(basePath, reqPath string) (string, error) {\n\tif strings.HasSuffix(reqPath, \"..\") || strings.Contains(reqPath, \"../\") {\n\t\treturn \"\", errors.New(\"access using relative path is not allowed\")\n\t}\n\treturn stdpath.Join(FixAndCleanPath(basePath), FixAndCleanPath(reqPath)), nil\n}\n","sourceCodeStart":60,"sourceCodeEnd":82,"githubUrl":"https://github.com/IceWhaleTech/CasaOS/blob/0d3b2f444ec0193193cf03eef6d43c6e35b0183e/pkg/utils/path.go#L60-L82","documentation":"JoinBasePath rejects the incoming request path because it contains a '..' segment: either the path ends with '..' or contains a '../' substring. This is an intentional path-traversal guard that prevents callers from escaping the configured base directory via relative segments before the path is joined and cleaned.","triggerScenarios":"Any request path like '../../etc/passwd', '/safe/../..', or '/dir/../other' passed to JoinBasePath. The check runs before stdpath.Join, so any traversal attempt (accidental or malicious) fails closed.","commonSituations":"Malicious input probing for directory traversal; benign client sending unclean paths containing '..' (e.g. concatenating user folder names without sanitization); URL-decoded paths where %2e%2e%2f becomes '../'.","solutions":["Sanitize request paths on the caller side: reject or clean segments containing '..' before calling JoinBasePath.","If legitimate, rewrite '..' segments using stdpath.Clean on the request path first and ensure the result stays under the base — but never bypass the guard for raw user input.","Return HTTP 400 to clients with a clear 'path traversal not allowed' message.","Audit that the guard covers encoded variants by decoding before validation."],"exampleFix":"// before\njoined, err := utils.JoinBasePath(basePath, reqPath)\n\n// after (validate first, then join)\nif strings.Contains(reqPath, \"..\") {\n\treturn http.StatusBadRequest, fmt.Errorf(\"path must not contain '..' segments\")\n}\njoined, err := utils.JoinBasePath(basePath, reqPath)","handlingStrategy":"validation","validationCode":"func IsSafeRelativePath(p string) bool {\n\tif p == \"\" { return true }\n\tfor _, seg := range strings.Split(p, \"/\") {\n\t\tif seg == \"..\" { return false }\n\t}\n\treturn true\n}\n\nif !IsSafeRelativePath(reqPath) { return http.StatusBadRequest }","typeGuard":"func IsSafeRelativePath(p string) bool {\n\tfor _, seg := range strings.Split(p, \"/\") {\n\t\tif seg == \"..\" {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"joined, err := utils.JoinBasePath(basePath, reqPath)\nif err != nil {\n\tif strings.Contains(err.Error(), \"relative path\") {\n\t\treturn http.StatusBadRequest, errors.New(\"path traversal rejected\") // never retry\n\t}\n\treturn http.StatusInternalServerError, err\n}","preventionTips":["URL-decode then validate every user-supplied path before joining","Reject '..' segments at the HTTP handler boundary with 400","After joining, verify the result still has the base path as prefix (defense in depth)"],"tags":["security","path-traversal","validation","filesystem"],"backgroundTag":null,"analyzedSha":"0d3b2f444ec0193193cf03eef6d43c6e35b0183e","analyzedAt":"2026-08-15T13:27:57.821Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}