{"record":{"id":"81eaf1ad0fc8d435","repo":"siyuan-note/siyuan","slug":"asset-path-is-required","errorCode":null,"errorMessage":"asset path is required","messagePattern":"asset path is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/assets.go","lineNumber":893,"sourceCode":"\t\tsort.Slice(ret, func(i, j int) bool {\n\t\t\treturn ret[i].Updated > ret[j].Updated\n\t\t})\n\t}\n\n\tif Conf.Search.Limit <= len(ret) {\n\t\tret = ret[:Conf.Search.Limit]\n\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}","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L875-L911","documentation":"Returned by ResolveDataAssetPath when assetPath is the empty string. ResolveDataAssetPath resolves a data-directory-relative asset path with strict traversal guards; an empty input is rejected before any path normalization. It is the first and most basic guard in the resolver.","triggerScenarios":"Calling ResolveDataAssetPath(\"\"); a caller forwarding an unvalidated query parameter; a deserialized JSON field that was omitted.","commonSituations":"An API endpoint accepted an optional asset path and forwarded it without checking presence; a refactor left a blank default.","solutions":["Check for empty/whitespace assetPath at the request boundary and return a 400.","Make the field required in the request schema."],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(path)\n\n// after\nif strings.TrimSpace(path) == \"\" {\n    return errors.New(\"asset path is required\")\n}\nrel, abs, err := model.ResolveDataAssetPath(path)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(assetPath) == \"\" {\n    return errors.New(\"asset path is required\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make asset path a required field in request schemas.","Reject empty values at the API boundary with a clear 400."],"tags":["validation","assets","input-validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}