{"record":{"id":"7ec57dd586b5f798","repo":"vxcontrol/pentagi","slug":"invalid-path","errorCode":null,"errorMessage":"invalid path","messagePattern":"invalid path","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"backend/pkg/flowfiles/files.go","lineNumber":142,"sourceCode":"\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) {\n\ttrimmedPath := strings.TrimSpace(containerPath)\n\tif trimmedPath == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path is required\")\n\t}\n\n\tnormalizedPath := strings.ReplaceAll(trimmedPath, \"\\\\\", \"/\")\n\tcleanPath := strings.TrimPrefix(path.Clean(\"/\"+normalizedPath), \"/\")\n\tif cleanPath == \".\" || cleanPath == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid path\")\n\t}\n\n\tparts := strings.Split(cleanPath, \"/\")\n\tfor i, part := range parts {\n\t\tcleanPart, err := validatePathComponent(part)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"invalid path component '%s': %w\", part, err)\n\t\t}\n\t\tparts[i] = cleanPart\n\t}\n\n\treturn path.Join(parts...), nil\n}\n\nfunc validatePathComponent(component string) (string, error) {\n\tcleanName := strings.TrimSpace(component)\n\tif cleanName == \".\" || cleanName == \"..\" || cleanName == \"/\" || cleanName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid file name\")","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/flowfiles/files.go#L124-L160","documentation":"SanitizeContainerCachePath normalizes backslashes, cleans the path against a virtual root, and rejects the result when it collapses to \".\" or empty — meaning the input was effectively a root (\",/\", \".\", \"/./\") with no real component. It returns 'invalid path' to prevent caching or copying the entire filesystem root.","triggerScenarios":"Calling SanitizeContainerCachePath with \".\", \"/\", \"\\\\\", \"/.\", \".//\", or any path that normalizes to the root after path.Clean(\"/\"+p) strips back to the virtual root.","commonSituations":"An LLM agent answers pull_flow_files with \"/\" or \".\" meaning 'everything'; a user drags a folder root into the UI and the client sends the root path; Windows-style paths reduced to a drive root like \"C:\\\" before reaching the sanitizer.","solutions":["Pass a specific file or subdirectory path (e.g. \"/var/log/app.log\"), not a filesystem root.","If 'pull everything' is intended, enumerate candidate paths first and call SanitizeContainerCachePath per entry.","Update the calling agent's prompt/tool schema to require a concrete file path rather than a root."],"exampleFix":"// before\npullFiles(containerPath: \"/\")\n// after\npullFiles(containerPath: \"/var/log/app.log\")","handlingStrategy":"validation","validationCode":"func isConcretePath(p string) bool {\n    n := path.Clean(\"/\" + strings.ReplaceAll(strings.TrimSpace(p), \"\\\\\", \"/\"))\n    return n != \"/\" && n != \"/.\"\n}","typeGuard":"func isRootish(p string) bool {\n    n := path.Clean(\"/\" + strings.ReplaceAll(strings.TrimSpace(p), \"\\\\\", \"/\"))\n    return n == \"/\" || n == \"/.\"\n}","tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"invalid path\") {\n        return fmt.Errorf(\"refusing root-like container path %q\", containerPath)\n    }\n    return err\n}","preventionTips":["Never accept \"/\", \".\", or \"\\\\\" as a container path.","Require tool schemas to mark path as required and non-root.","Enumerate files explicitly when 'everything' is requested."],"tags":["go","path-validation","path-traversal"],"backgroundTag":"invalid-path-input","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}