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 "", errView on GitHub (pinned to ea665308ba)
Solutions
- Strip the leading '/' (or the base-directory prefix) from the input before calling SanitizeResourcePath
- Extract only the relative portion, e.g. with strings.TrimPrefix(p, baseDir) or filepath.Rel(baseDir, p)
- Validate user input at the UI/API boundary to reject absolute paths early
- 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
- Never pass raw absolute filesystem paths into resource APIs — pass names relative to the storage root
- Normalize Windows backslashes yourself if input can come from Windows clients
- Validate user-supplied paths at the API boundary with the same rules the library uses
- Log rejected inputs to spot clients sending absolute paths systematically
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
- path must be relative (no leading /)
- path escapes the flow data directory
- invalid path
- path must not contain parent directory traversal
- Token.CreationDisabled
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/ac32046866880627.
Report an issue: GitHub.