vxcontrol/pentagi · error

path escapes the flow data directory

Error message

path escapes the flow data directory

What it means

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.

Source

Thrown at backend/pkg/flowfiles/files.go:115

func ResolveCachedPath(dataDir string, flowID uint64, reqPath string) (string, error) {
	if strings.TrimSpace(reqPath) == "" {
		return "", errors.New("path query parameter is required")
	}

	cleaned := filepath.Clean(filepath.FromSlash(strings.ReplaceAll(reqPath, "\\", "/")))
	if filepath.IsAbs(cleaned) {
		return "", fmt.Errorf("path must be relative (no leading /)")
	}

	parts := strings.SplitN(cleaned, string(filepath.Separator), 2)
	if parts[0] != UploadsDirName && parts[0] != ContainerDirName && parts[0] != ResourcesDirName {
		return "", fmt.Errorf("path must start with '%s', '%s', or '%s'", UploadsDirName, ContainerDirName, ResourcesDirName)
	}

	flowDataDir := FlowDataDir(dataDir, flowID)
	absPath := filepath.Join(flowDataDir, cleaned)
	if !IsWithinDir(absPath, flowDataDir) {
		return "", fmt.Errorf("path escapes the flow data directory")
	}

	return absPath, nil
}

func SanitizeFileName(fileName string) (string, error) {
	trimmedName := strings.TrimSpace(fileName)
	if trimmedName == "" {
		return "", fmt.Errorf("file name is required")
	}

	normalizedName := strings.ReplaceAll(trimmedName, "\\", "/")
	cleanName := path.Base(path.Clean("/" + normalizedName))

	return validatePathComponent(cleanName)
}

func SanitizeContainerCachePath(containerPath string) (string, error) {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Remove '..' segments; use only List-returned paths
  2. Keep the path under the chosen root: uploads/, container/, or resources/
  3. Treat repeated occurrences as a security probe and log/alert on the requester
  4. Optionally reject any '..' in the raw input before calling the API

Example fix

// before
resolveCachedPath(flowID, "uploads/../../shared/file.txt")
// after
resolveCachedPath(flowID, "uploads/file.txt") // stay inside the flow's cache root
Defensive patterns

Strategy: validation

Validate before calling

func containsDotDot(p string) bool {
    for _, seg := range strings.Split(strings.ReplaceAll(p, "\\", "/"), "/") {
        if seg == ".." { return true }
    }
    return false
}
// reject before calling: if containsDotDot(reqPath) { return errors.New("traversal rejected") }

Type guard

func isTraversalError(err error) bool { return err != nil && strings.Contains(err.Error(), "path escapes the flow data directory") }

Try / catch

abs, err := flowfiles.ResolveCachedPath(dataDir, flowID, reqPath)
if err != nil && strings.Contains(err.Error(), "path escapes the flow data directory") {
    log.WithField("path", reqPath).Warn("path traversal attempt blocked")
    http.Error(w, "invalid path", http.StatusBadRequest)
    return
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


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