{"record":{"id":"ac32046866880627","repo":"vxcontrol/pentagi","slug":"path-must-be-relative","errorCode":null,"errorMessage":"path must be relative","messagePattern":"path must be relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/resources/resources.go","lineNumber":147,"sourceCode":"// SanitizeResourcePath normalises a client-supplied virtual path and ensures it\n// is safe to use:\n//   - trims whitespace\n//   - converts backslashes to forward slashes\n//   - cleans the path (removes .., double slashes, etc.)\n//   - rejects absolute paths, dot-only components, and paths that exceed MaxPathLength\n//   - returns an error for the empty path\nfunc SanitizeResourcePath(p string) (string, error) {\n\ttrimmed := strings.TrimSpace(p)\n\tif trimmed == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path must not be empty\")\n\t}\n\tif len(trimmed) > MaxPathLength {\n\t\treturn \"\", fmt.Errorf(\"path exceeds maximum allowed length of %d characters\", MaxPathLength)\n\t}\n\n\tnormalized := strings.ReplaceAll(trimmed, \"\\\\\", \"/\")\n\tif strings.HasPrefix(normalized, \"/\") {\n\t\treturn \"\", fmt.Errorf(\"path must be relative\")\n\t}\n\tfor _, part := range strings.Split(normalized, \"/\") {\n\t\tif part == \"..\" {\n\t\t\treturn \"\", fmt.Errorf(\"path must not contain parent directory traversal\")\n\t\t}\n\t}\n\tcleaned := path.Clean(\"/\" + normalized)\n\t// Remove the leading \"/\" we added for Clean, making the path relative.\n\trel := strings.TrimPrefix(cleaned, \"/\")\n\tif rel == \"\" || rel == \".\" {\n\t\treturn \"\", fmt.Errorf(\"invalid path\")\n\t}\n\n\t// Validate every path component.\n\tparts := strings.Split(rel, \"/\")\n\tfor _, part := range parts {\n\t\tif err := validatePathComponent(part); err != nil {\n\t\t\treturn \"\", err","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/resources/resources.go#L129-L165","documentation":"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.","triggerScenarios":"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 '/'.","commonSituations":"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.","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"],"exampleFix":"// before\nname, err := resources.SanitizeResourcePath(\"/uploads/report.pdf\")\n// after\nrel := strings.TrimPrefix(\"/uploads/report.pdf\", \"/\")\nname, err := resources.SanitizeResourcePath(rel) // \"uploads/report.pdf\"","handlingStrategy":"validation","validationCode":"func isRelativeResourcePath(p string) bool {\n\tnorm := strings.ReplaceAll(strings.TrimSpace(p), \"\\\\\", \"/\")\n\treturn norm != \"\" && !strings.HasPrefix(norm, \"/\")\n}","typeGuard":null,"tryCatchPattern":"name, err := resources.SanitizeResourcePath(input)\nif err != nil {\n\tif strings.Contains(err.Error(), \"path must be relative\") {\n\t\treturn fmt.Errorf(\"%q is not a relative path: %w\", input, err)\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["path-validation","security","path-traversal"],"backgroundTag":"absolute-path-rejected","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}