{"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":1032,"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":1014,"sourceCodeEnd":1050,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1014-L1050","documentation":"ResolveDataAssetPath only accepts paths relative to the workspace data directory. After converting to the OS-native form, it rejects any input that is absolute (filepath.IsAbs), carries a Windows volume name (C:...), or starts with a path separator. The error echoes the offending path so the caller can correct it.","triggerScenarios":"Passing an absolute path like \"/home/user/SiYuan/data/assets/img.png\" or \"C:\\\\data\\\\assets\\\\img.png\", a path starting with a leading slash even if otherwise relative (\"/assets/img.png\"), or a full file:// URL to ResolveDataAssetPath (directly or through assetStat, PrepareAgentMessageImage, ResolveUnusedDataAssetPath, etc.).","commonSituations":"Scripts or plugins use paths copied from the OS file manager or from an API response containing absolute paths; code builds paths with filepath.Join(util.DataDir, ...) before calling the resolver, double-qualifying the path; Windows clients send backslash-prefixed drive paths.","solutions":["Strip the data directory prefix and pass only the remainder, e.g. \"assets/img.png\" instead of \"/abs/path/to/workspace/data/assets/img.png\"","Use filepath.Rel(util.DataDir, absPath) to convert an absolute path you already have into the required data-relative form before calling","Remove leading slashes / file:// scheme from the input; for URLs, extract and decode the path portion first","If the path comes from GetAssetAbsPath or similar, use the corresponding relative-path accessor instead"],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(\"/home/me/siyuan/data/assets/pic.png\")\n// after\nrelPath, _ := filepath.Rel(util.DataDir, \"/home/me/siyuan/data/assets/pic.png\") // \"assets/pic.png\"\nrel, abs, err := model.ResolveDataAssetPath(filepath.ToSlash(relPath))","handlingStrategy":"validation","validationCode":"func toDataRelative(absPath, dataDir string) (string, error) {\n    rel, err := filepath.Rel(dataDir, absPath)\n    if err != nil || strings.HasPrefix(rel, \"..\") || filepath.IsAbs(rel) {\n        return \"\", fmt.Errorf(\"not under data dir: %s\", absPath)\n    }\n    return filepath.ToSlash(rel), nil\n}","typeGuard":"func isRelative(p string) bool {\n    n := filepath.FromSlash(p)\n    return !filepath.IsAbs(n) && filepath.VolumeName(n) == \"\" && !os.IsPathSeparator(n[0])\n}","tryCatchPattern":"rel, abs, err := model.ResolveDataAssetPath(p)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"asset path must be relative\") {\n        p = strings.TrimPrefix(p, util.DataDir+string(filepath.Separator))\n        rel, abs, err = model.ResolveDataAssetPath(p)\n    }\n    if err != nil { return err }\n}","preventionTips":["Store and transmit asset paths in data-relative slash form everywhere in your code","Never call filepath.Join(util.DataDir, ...) before handing the path to the resolver","On Windows, remember drive letters make a path absolute; strip them at ingestion","Convert API-returned absolute paths back to relative form before re-submitting them"],"tags":["assets","path-validation","absolute-path"],"backgroundTag":"invalid-argument-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}