{"record":{"id":"441311f3950f9795","repo":"crowdsecurity/crowdsec","slug":"q-must-be-a-relative-path","errorCode":null,"errorMessage":"%q: must be a relative path","messagePattern":"%q: must be a relative path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cwhub/safepath.go","lineNumber":23,"sourceCode":"\t\"path/filepath\"\n\t\"os\"\n\t\"strings\"\n)\n\n// SafePath returns a joined path and ensures that it does not escape the base directory.\n// We can't use the traversal-resistant methods in \"os.Root\" because install link targets are outside their base directories\n// (installdir -> hubdir), which would not be allowed if hubdir is not inside installdir.\nfunc SafePath(baseDir, relPath string) (string, error) {\n\tabsBase, err := filepath.Abs(filepath.Clean(baseDir))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif filepath.IsAbs(relPath) ||\n\t\t// on windows, IsAbs fails for paths beginning with \"/\", since it's the root of the drive\n\t\tstrings.HasPrefix(relPath, string(os.PathSeparator)) ||\n\t\tstrings.HasPrefix(relPath, \"/\") {\n\t\treturn \"\", fmt.Errorf(\"%q: must be a relative path\", relPath)\n\t}\n\n\tabsFilePath, err := filepath.Abs(filepath.Join(absBase, relPath))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\trel, err := filepath.Rel(absBase, absFilePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif strings.HasPrefix(rel, \"..\") {\n\t\treturn \"\", fmt.Errorf(\"%q: path escapes base directory %q\", relPath, baseDir)\n\t}\n\n\treturn absFilePath, nil\n}","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/cwhub/safepath.go#L5-L41","documentation":"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.","triggerScenarios":"Calling SafePath (via PathForInstall, PathForDownload, downloadDataSet, or anonymous download handlers) with a path like \"/etc/passwd\", \"C:\\\\x\", or one beginning with \"/\".","commonSituations":"Untrusted index/index entry paths; a malicious or corrupt hub index containing absolute remote/local paths; user-supplied install path flags with absolute values.","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"],"exampleFix":"// before\nSafePath(baseDir, \"/etc/crowdsec/parsers/x.yaml\")\n// after\nSafePath(baseDir, \"parsers/s01-parse/x.yaml\")","handlingStrategy":"validation","validationCode":"func isRelative(p string) bool {\n    return p != \"\" && !filepath.IsAbs(p) && !strings.HasPrefix(p, \"/\") && !strings.Contains(p, \"\\\\\\\\\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["security","path","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}