{"record":{"id":"6d5bd7c8eef19d31","repo":"vxcontrol/pentagi","slug":"path-escapes-the-flow-data-directory","errorCode":null,"errorMessage":"path escapes the flow data directory","messagePattern":"path escapes the flow data directory","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"backend/pkg/flowfiles/files.go","lineNumber":115,"sourceCode":"func ResolveCachedPath(dataDir string, flowID uint64, reqPath string) (string, error) {\n\tif strings.TrimSpace(reqPath) == \"\" {\n\t\treturn \"\", errors.New(\"path query parameter is required\")\n\t}\n\n\tcleaned := filepath.Clean(filepath.FromSlash(strings.ReplaceAll(reqPath, \"\\\\\", \"/\")))\n\tif filepath.IsAbs(cleaned) {\n\t\treturn \"\", fmt.Errorf(\"path must be relative (no leading /)\")\n\t}\n\n\tparts := strings.SplitN(cleaned, string(filepath.Separator), 2)\n\tif parts[0] != UploadsDirName && parts[0] != ContainerDirName && parts[0] != ResourcesDirName {\n\t\treturn \"\", fmt.Errorf(\"path must start with '%s', '%s', or '%s'\", UploadsDirName, ContainerDirName, ResourcesDirName)\n\t}\n\n\tflowDataDir := FlowDataDir(dataDir, flowID)\n\tabsPath := filepath.Join(flowDataDir, cleaned)\n\tif !IsWithinDir(absPath, flowDataDir) {\n\t\treturn \"\", fmt.Errorf(\"path escapes the flow data directory\")\n\t}\n\n\treturn absPath, nil\n}\n\nfunc SanitizeFileName(fileName string) (string, error) {\n\ttrimmedName := strings.TrimSpace(fileName)\n\tif trimmedName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"file name is required\")\n\t}\n\n\tnormalizedName := strings.ReplaceAll(trimmedName, \"\\\\\", \"/\")\n\tcleanName := path.Base(path.Clean(\"/\" + normalizedName))\n\n\treturn validatePathComponent(cleanName)\n}\n\nfunc SanitizeContainerCachePath(containerPath string) (string, error) {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/flowfiles/files.go#L97-L133","documentation":"After joining the cleaned path with the flow data dir, ResolveCachedPath re-checks containment with IsWithinDir. This error means the resulting absolute path would escape the per-flow data directory (e.g. via '..' segments in an allowed root), so it is refused to prevent cross-flow or host file access.","triggerScenarios":"Paths containing '..' segments that survive Clean and step outside the flow dir, e.g. 'uploads/../../other-flow/secret.txt', despite starting with a valid root.","commonSituations":"Hand-crafted malicious requests attempting path traversal; buggy client code concatenating '../' when building relative paths; symlinks inside the cache pointing outside (if IsWithinDir follows them).","solutions":["Remove '..' segments; use only List-returned paths","Keep the path under the chosen root: uploads/, container/, or resources/","Treat repeated occurrences as a security probe and log/alert on the requester","Optionally reject any '..' in the raw input before calling the API"],"exampleFix":"// before\nresolveCachedPath(flowID, \"uploads/../../shared/file.txt\")\n// after\nresolveCachedPath(flowID, \"uploads/file.txt\") // stay inside the flow's cache root","handlingStrategy":"validation","validationCode":"func containsDotDot(p string) bool {\n    for _, seg := range strings.Split(strings.ReplaceAll(p, \"\\\\\", \"/\"), \"/\") {\n        if seg == \"..\" { return true }\n    }\n    return false\n}\n// reject before calling: if containsDotDot(reqPath) { return errors.New(\"traversal rejected\") }","typeGuard":"func isTraversalError(err error) bool { return err != nil && strings.Contains(err.Error(), \"path escapes the flow data directory\") }","tryCatchPattern":"abs, err := flowfiles.ResolveCachedPath(dataDir, flowID, reqPath)\nif err != nil && strings.Contains(err.Error(), \"path escapes the flow data directory\") {\n    log.WithField(\"path\", reqPath).Warn(\"path traversal attempt blocked\")\n    http.Error(w, \"invalid path\", http.StatusBadRequest)\n    return\n}","preventionTips":["Reject any '..' segment in raw input before resolution","Serve files only through ResolveCachedPath — never join user paths directly","Log and alert on traversal attempts as potential security probes","Keep symlinks out of the flow cache directories"],"tags":["security","path-traversal","flowfiles"],"backgroundTag":"path-traversal-attempt","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}