{"record":{"id":"539c44a72be3b7cc","repo":"wavetermdev/waveterm","slug":"path-cannot-start-with-or","errorCode":null,"errorMessage":"path cannot start with ~, ., or ..","messagePattern":"path cannot start with ~, \\., or \\.\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/remote/fileshare/fsutil/fsutil.go","lineNumber":48,"sourceCode":"\t}\n\n\tlastSlash := strings.LastIndex(hostAndPath, fspath.Separator)\n\tif lastSlash <= 0 {\n\t\treturn \"\"\n\t}\n\treturn hostAndPath[:lastSlash+1]\n}\n\n// CleanPathPrefix corrects paths for prefix filesystems (i.e. ones that don't have directories)\nfunc CleanPathPrefix(path string) (string, error) {\n\tif path == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif strings.HasPrefix(path, fspath.Separator) {\n\t\tpath = path[1:]\n\t}\n\tif strings.HasPrefix(path, \"~\") || strings.HasPrefix(path, \".\") || strings.HasPrefix(path, \"..\") {\n\t\treturn \"\", fmt.Errorf(\"path cannot start with ~, ., or ..\")\n\t}\n\tvar newParts []string\n\tfor _, part := range strings.Split(path, fspath.Separator) {\n\t\tif part == \"..\" {\n\t\t\tif len(newParts) > 0 {\n\t\t\t\tnewParts = newParts[:len(newParts)-1]\n\t\t\t}\n\t\t} else if part != \".\" {\n\t\t\tnewParts = append(newParts, part)\n\t\t}\n\t}\n\treturn fspath.Join(newParts...), nil\n}\n\nfunc ReadFileStream(ctx context.Context, readCh <-chan wshrpc.RespOrErrorUnion[wshrpc.FileData], fileInfoCallback func(finfo wshrpc.FileInfo), dirCallback func(entries []*wshrpc.FileInfo) error, fileCallback func(data io.Reader) error) error {\n\tvar fileData *wshrpc.FileData\n\tfirstPk := true\n\tisDir := false","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/remote/fileshare/fsutil/fsutil.go#L30-L66","documentation":"CleanPathPrefix sanitizes a remote file path for safe sharing. After stripping a leading separator it rejects paths that begin with ~, ., or .. because such prefixes can escape the intended shared root or reference ambiguous home-relative locations.","triggerScenarios":"Calling CleanPathPrefix (used in fileshare path handling) with a path like \"~/file.txt\", \".hidden\", \"../etc/passwd\", or \"..foo\".","commonSituations":"Client sends a home-relative path assuming ~ expansion; relative paths constructed with \"./\" prefixes; path traversal attempt in a shared-file feature; user pastes a shell-style path into a file-share input.","solutions":["Expand ~ client-side before sending (resolve to an absolute path)","Convert the path to absolute within the allowed shared root","Remove ./ prefixes and resolve .. segments before calling","Reject or re-prompt the user for a valid absolute path"],"exampleFix":"// before\np, err := fsutil.CleanPathPrefix(\"~/notes.txt\") // error\n// after\nabs, _ := filepath.Abs(filepath.Join(homeDir, \"notes.txt\"))\np, err := fsutil.CleanPathPrefix(strings.TrimPrefix(abs, \"/\"))","handlingStrategy":"validation","validationCode":"func validSharePath(p string) bool {\n    base := filepath.Base(p)\n    return base != \"\" && !strings.HasPrefix(base, \"~\") && !strings.HasPrefix(base, \".\")\n}","typeGuard":"func isCleanSharePath(p string) bool {\n    return !strings.HasPrefix(p, \"~\") && !strings.HasPrefix(p, \"./\") && !strings.HasPrefix(p, \"../\")\n}","tryCatchPattern":"p, err := fsutil.CleanPathPrefix(raw)\nif err != nil && strings.Contains(err.Error(), \"cannot start with\") {\n    return fmt.Errorf(\"please provide an absolute path inside the shared root\")\n}","preventionTips":["Always send absolute paths within the shared root","Expand ~ on the client before sending","Sanitize user-supplied paths and reject traversal patterns"],"tags":["go","path","security"],"backgroundTag":"path-traversal-rejected","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}