crowdsecurity/crowdsec · error
%q: must be a relative path
Error message
%q: must be a relative path
What it means
SafePath joins a user-supplied relative path onto a base directory and refuses absolute paths. Because Windows treats "/x" as drive-rooted, the check also rejects any path starting with a path separator outright, ensuring the result stays inside the base directory namespace.
Source
Thrown at pkg/cwhub/safepath.go:23
"path/filepath"
"os"
"strings"
)
// SafePath returns a joined path and ensures that it does not escape the base directory.
// We can't use the traversal-resistant methods in "os.Root" because install link targets are outside their base directories
// (installdir -> hubdir), which would not be allowed if hubdir is not inside installdir.
func SafePath(baseDir, relPath string) (string, error) {
absBase, err := filepath.Abs(filepath.Clean(baseDir))
if err != nil {
return "", err
}
if filepath.IsAbs(relPath) ||
// on windows, IsAbs fails for paths beginning with "/", since it's the root of the drive
strings.HasPrefix(relPath, string(os.PathSeparator)) ||
strings.HasPrefix(relPath, "/") {
return "", fmt.Errorf("%q: must be a relative path", relPath)
}
absFilePath, err := filepath.Abs(filepath.Join(absBase, relPath))
if err != nil {
return "", err
}
rel, err := filepath.Rel(absBase, absFilePath)
if err != nil {
return "", err
}
if strings.HasPrefix(rel, "..") {
return "", fmt.Errorf("%q: path escapes base directory %q", relPath, baseDir)
}
return absFilePath, nil
}View on GitHub (pinned to 909b515798)
Solutions
- Pass a relative path (e.g. "parsers/s01-parse/x.yaml") instead of an absolute one
- Strip the base-directory prefix from stored absolute paths before calling SafePath
- Use filepath.ToSlash and filepath.Rel(base, p) to normalize input
- Treat the error as a security signal — investigate where the absolute path came from
Example fix
// before SafePath(baseDir, "/etc/crowdsec/parsers/x.yaml") // after SafePath(baseDir, "parsers/s01-parse/x.yaml")
Defensive patterns
Strategy: validation
Validate before calling
func isRelative(p string) bool {
return p != "" && !filepath.IsAbs(p) && !strings.HasPrefix(p, "/") && !strings.Contains(p, "\\\\")
} Prevention
- Store repo-relative paths in indexes and manifests
- Normalize with filepath.ToSlash before handing paths to SafePath
- Never build paths from raw untrusted input without cleaning
When it happens
Trigger: Calling SafePath (via PathForInstall, PathForDownload, downloadDataSet, or anonymous download handlers) with a path like "/etc/passwd", "C:\\x", or one beginning with "/".
Common situations: Untrusted index/index entry paths; a malicious or corrupt hub index containing absolute remote/local paths; user-supplied install path flags with absolute values.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- schema filename %q must be relative to %s and stay within it
- %q: path escapes base directory %q
- group_name is mandatory for CloudwatchSource
- path must start with /
- basic_auth is selected, but basic_auth is not provided
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/441311f3950f9795.
Report an issue: GitHub.