{"record":{"id":"1a662173a2967dab","repo":"vxcontrol/pentagi","slug":"path-must-start-with-s-s-or-s","errorCode":null,"errorMessage":"path must start with '%s', '%s', or '%s'","messagePattern":"path must start with '(.+?)', '(.+?)', or '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"backend/pkg/flowfiles/files.go","lineNumber":109,"sourceCode":"}\n\nfunc FlowResourcesDir(dataDir string, flowID uint64) string {\n\treturn filepath.Join(FlowDataDir(dataDir, flowID), ResourcesDirName)\n}\n\nfunc 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, \"\\\\\", \"/\")","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/flowfiles/files.go#L91-L127","documentation":"ResolveCachedPath requires the first path component to be one of the three known cache roots: 'uploads', 'container', or 'resources'. Any other prefix cannot be resolved to a flow cache location and is rejected with this formatted message naming the three allowed roots.","triggerScenarios":"Passing a path whose first segment is anything else, e.g. 'tmp/file.txt', 'flow-data/x', or an empty-ish segment like './file.txt' after Clean.","commonSituations":"API consumers inventing their own prefixes; frontend hardcoding a legacy directory name that was renamed; concatenating flow IDs or other prefixes onto the path.","solutions":["Prefix the path with one of: uploads/, container/, resources/","Take paths directly from the List endpoint output, which always uses these roots","Fix client constants that reference old/renamed directory names","Check for accidental './' or empty leading segments in the constructed path"],"exampleFix":"// before\nresolveCachedPath(flowID, \"files/report.pdf\")\n// after\nresolveCachedPath(flowID, \"resources/report.pdf\")","handlingStrategy":"validation","validationCode":"func hasValidRoot(p string) bool {\n    first := strings.SplitN(strings.ReplaceAll(p, \"\\\\\", \"/\"), \"/\", 2)[0]\n    return first == \"uploads\" || first == \"container\" || first == \"resources\"\n}","typeGuard":"func isBadRootError(err error) bool { return err != nil && strings.Contains(err.Error(), \"path must start with\") }","tryCatchPattern":"abs, err := flowfiles.ResolveCachedPath(dataDir, flowID, reqPath)\nif err != nil {\n    if strings.Contains(err.Error(), \"path must start with\") {\n        http.Error(w, \"path must start with uploads/, container/, or resources/\", http.StatusBadRequest)\n        return\n    }\n    return err\n}","preventionTips":["Keep a single shared constant for allowed roots in frontend and backend","Prefer consuming List output over constructing paths manually","Update clients whenever cache root names change","Validate the first path segment client-side before calling the API"],"tags":["validation","path-traversal","flowfiles"],"backgroundTag":"invalid-path-format","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}