vxcontrol/pentagi · error
path must not contain parent directory traversal
Error message
path must not contain parent directory traversal
What it means
SanitizeResourcePath rejects any path containing a '..' segment after normalizing backslashes to '/'. Parent-directory traversal would allow a crafted resource name to escape the storage root. The check runs before path.Clean so even obfuscated-but-plain '..' segments are caught.
Source
Thrown at backend/pkg/resources/resources.go:151
// - cleans the path (removes .., double slashes, etc.)
// - rejects absolute paths, dot-only components, and paths that exceed MaxPathLength
// - returns an error for the empty path
func SanitizeResourcePath(p string) (string, error) {
trimmed := strings.TrimSpace(p)
if trimmed == "" {
return "", fmt.Errorf("path must not be empty")
}
if len(trimmed) > MaxPathLength {
return "", fmt.Errorf("path exceeds maximum allowed length of %d characters", MaxPathLength)
}
normalized := strings.ReplaceAll(trimmed, "\\", "/")
if strings.HasPrefix(normalized, "/") {
return "", fmt.Errorf("path must be relative")
}
for _, part := range strings.Split(normalized, "/") {
if part == ".." {
return "", fmt.Errorf("path must not contain parent directory traversal")
}
}
cleaned := path.Clean("/" + normalized)
// Remove the leading "/" we added for Clean, making the path relative.
rel := strings.TrimPrefix(cleaned, "/")
if rel == "" || rel == "." {
return "", fmt.Errorf("invalid path")
}
// Validate every path component.
parts := strings.Split(rel, "/")
for _, part := range parts {
if err := validatePathComponent(part); err != nil {
return "", err
}
}
return rel, nilView on GitHub (pinned to ea665308ba)
Solutions
- Reject or sanitize the input upstream: strip or refuse any '..' segments before calling the API
- Use path.Base or the library's SanitizeResourceFileName for bare filenames
- Log the rejected input — it usually indicates a malicious or buggy client
- If traversal is legitimately required, resolve the destination yourself and verify it stays within the root
Example fix
// before
name, err := resources.SanitizeResourcePath(userInput) // "../../etc/passwd"
// after
if strings.Contains(userInput, "..") {
return fmt.Errorf("rejected suspicious path %q", userInput)
}
name, err := resources.SanitizeResourcePath(userInput) Defensive patterns
Strategy: validation
Validate before calling
func hasTraversal(p string) bool {
norm := strings.ReplaceAll(p, "\\", "/")
for _, part := range strings.Split(norm, "/") {
if part == ".." {
return true
}
}
return false
} Try / catch
name, err := resources.SanitizeResourcePath(userInput)
if err != nil {
if strings.Contains(err.Error(), "parent directory traversal") {
log.Warn("path traversal attempt blocked", "input", userInput)
return status.Errorf(codes.InvalidArgument, "invalid resource path")
}
return err
} Prevention
- Treat any '..' in user input as an attack signal and reject it before calling the library
- For ZIP extraction, sanitize every entry name with the library before writing (Zip Slip defense)
- Never build paths by string concatenation of user input with a base directory
- Add automated tests with traversal payloads (../../etc/passwd, a\..\..\x) to your upload handlers
When it happens
Trigger: Calling SanitizeResourcePath (or AddResourceFromFlow, ZipResources, SanitizeResourceDir which delegate to it) with values like "../../etc/passwd", "a/../../b", or any user-controlled filename containing a literal '..' path segment.
Common situations: Untrusted filenames coming from ZIP archives or HTTP uploads being stored directly; clients attempting to escape the resources directory; archive-extraction code (Zip Slip) feeding raw entry names into the API.
Related errors
- path must be relative (no leading /)
- path escapes the flow data directory
- path must be relative
- path query parameter is required
- Token.CreationDisabled
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/a729a93efdbd1d43.
Report an issue: GitHub.