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, nil

View on GitHub (pinned to ea665308ba)

Solutions

  1. Reject or sanitize the input upstream: strip or refuse any '..' segments before calling the API
  2. Use path.Base or the library's SanitizeResourceFileName for bare filenames
  3. Log the rejected input — it usually indicates a malicious or buggy client
  4. 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

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


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/a729a93efdbd1d43. Report an issue: GitHub.