vxcontrol/pentagi · error

path must be relative

Error message

path must be relative

What it means

SanitizeResourcePath rejects any path that starts with a '/' (after backslashes are normalized to forward slashes). The library only accepts relative resource paths so they can be safely joined under an internal storage root; absolute paths could escape that root. This is a deliberate security guard against path escape via absolute path injection.

Source

Thrown at backend/pkg/resources/resources.go:147

// SanitizeResourcePath normalises a client-supplied virtual path and ensures it
// is safe to use:
//   - trims whitespace
//   - converts backslashes to forward slashes
//   - 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

View on GitHub (pinned to ea665308ba)

Solutions

  1. Strip the leading '/' (or the base-directory prefix) from the input before calling SanitizeResourcePath
  2. Extract only the relative portion, e.g. with strings.TrimPrefix(p, baseDir) or filepath.Rel(baseDir, p)
  3. Validate user input at the UI/API boundary to reject absolute paths early
  4. If a full absolute path is genuinely needed, resolve it outside this sanitizer with explicit authorization

Example fix

// before
name, err := resources.SanitizeResourcePath("/uploads/report.pdf")
// after
rel := strings.TrimPrefix("/uploads/report.pdf", "/")
name, err := resources.SanitizeResourcePath(rel) // "uploads/report.pdf"
Defensive patterns

Strategy: validation

Validate before calling

func isRelativeResourcePath(p string) bool {
	norm := strings.ReplaceAll(strings.TrimSpace(p), "\\", "/")
	return norm != "" && !strings.HasPrefix(norm, "/")
}

Try / catch

name, err := resources.SanitizeResourcePath(input)
if err != nil {
	if strings.Contains(err.Error(), "path must be relative") {
		return fmt.Errorf("%q is not a relative path: %w", input, err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling SanitizeResourcePath (directly or via Resources, SanitizeResourceDir, ZipResources, AddResourceFromFlow) with a value like "/etc/passwd", "C:\\data\\file" (backslash-normalized to "/C:/data/file"), or any user-supplied string that begins with '/'.

Common situations: Passing an absolute filesystem path from a local upload picker straight into the API; concatenating a configured base directory with a filename instead of passing just the filename; Windows-style paths from clients being normalized into root-prefixed strings.

Related errors


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