{"record":{"id":"aab882f460cda625","repo":"siyuan-note/siyuan","slug":"asset-path-must-be-relative-to-data-directory-s","errorCode":null,"errorMessage":"asset path must be relative to data directory: %s","messagePattern":"asset path must be relative to data directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":900,"sourceCode":"\t}\n\treturn\n}\n\nfunc GetAssetAbsPath(relativePath string) (string, error) {\n\treturn GetAssetAbsPathWithOpt(relativePath, false)\n}\n\n// ResolveDataAssetPath 解析 data 相对资源路径，并确保目标位于全局或普通笔记本的资源目录中。\nfunc ResolveDataAssetPath(assetPath string) (relativePath, absPath string, err error) {\n\tif assetPath == \"\" {\n\t\terr = errors.New(\"asset path is required\")\n\t\treturn\n\t}\n\n\tnativePath := filepath.FromSlash(assetPath)\n\tif filepath.IsAbs(nativePath) || filepath.VolumeName(nativePath) != \"\" ||\n\t\t(len(nativePath) > 0 && os.IsPathSeparator(nativePath[0])) {\n\t\terr = fmt.Errorf(\"asset path must be relative to data directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tnativePath = filepath.Clean(nativePath)\n\tabsPath = filepath.Join(util.DataDir, nativePath)\n\tdataRelativePath, relErr := filepath.Rel(util.DataDir, absPath)\n\tif relErr != nil || dataRelativePath == \".\" || dataRelativePath == \"..\" ||\n\t\tstrings.HasPrefix(dataRelativePath, \"..\"+string(filepath.Separator)) {\n\t\terr = fmt.Errorf(\"asset path escapes data directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tparts := strings.Split(filepath.ToSlash(dataRelativePath), \"/\")\n\tassetDirIndex := -1\n\tswitch {\n\tcase len(parts) > 1 && parts[0] == \"assets\":\n\t\tassetDirIndex = 0\n\tcase len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):","sourceCodeStart":882,"sourceCodeEnd":918,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L882-L918","documentation":"Returned by ResolveDataAssetPath when the path is absolute, has a Windows volume name (C:), or begins with a path separator. ResolveDataAssetPath only accepts paths relative to util.DataDir; absolute inputs are rejected as the first anti-traversal layer. The offending path is interpolated.","triggerScenarios":"Passing \"/etc/passwd\", \"C:\\\\Windows\\\\x\", \"/home/user/data/assets/x.png\", or any leading-\"/\" path. These would bypass the data-dir containment if joined naively.","commonSituations":"A client sends a full filesystem path instead of a data-relative one; a debug tooling path leaks through; an attempt at path traversal via absolute path.","solutions":["Strip leading slashes and volume names at the call site, or reject them with a 400.","Only ever pass data-directory-relative paths (e.g. \"assets/x.png\" or \"<boxID>/assets/x.png\")."],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(\"/data/assets/x.png\")\n\n// after — pass a data-relative path\nrel, abs, err := model.ResolveDataAssetPath(\"assets/x.png\")","handlingStrategy":"validation","validationCode":"np := filepath.FromSlash(assetPath)\nif filepath.IsAbs(np) || filepath.VolumeName(np) != \"\" || (len(np) > 0 && os.IsPathSeparator(np[0])) {\n    return fmt.Errorf(\"asset path must be data-relative: %s\", assetPath)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass data-directory-relative paths to ResolveDataAssetPath.","Strip leading separators and volume names from any user-supplied input upstream.","Treat absolute paths from clients as malformed, not as a convenience."],"tags":["validation","security","assets","path-traversal"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}