{"record":{"id":"50779f10dc9f0cad","repo":"siyuan-note/siyuan","slug":"s-is-not-an-asset-path-must-start-with-assets","errorCode":null,"errorMessage":"[%s] is not an asset path (must start with assets/)","messagePattern":"\\[(.+?)\\] is not an asset path \\(must start with assets/\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1084,"sourceCode":"\tcleanPath = filepath.ToSlash(relativePath)\n\treturn\n}\n\n// GetAssetAbsPathInBox 在指定 box 内解析资源绝对路径，不进行全局遍历。\n// relativePath 必须以 assets/ 前缀开头，boxID 为空且路径没有 box 查询参数时只解析普通/全局资源，不遍历加密 box。\n// 加密 box 直接从 <boxID>/assets/ 查找，不依赖后缀匹配。\nfunc GetAssetAbsPathInBox(relativePath, boxID string) (string, error) {\n\tvar err error\n\trelativePath, boxID, err = assetPathAndBox(relativePath, boxID)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\trelativePath = path.Clean(relativePath)\n\tif relativePath == \".\" || strings.HasPrefix(relativePath, \"../\") || relativePath == \"..\" || path.IsAbs(relativePath) {\n\t\treturn \"\", fmt.Errorf(\"[%s] is not an asset path\", relativePath)\n\t}\n\tif !strings.HasPrefix(relativePath, \"assets/\") {\n\t\treturn \"\", fmt.Errorf(\"[%s] is not an asset path (must start with assets/)\", relativePath)\n\t}\n\tif boxID != \"\" && !ast.IsNodeIDPattern(boxID) {\n\t\treturn \"\", fmt.Errorf(\"[%s] is not a box id\", boxID)\n\t}\n\n\tif boxID == \"\" {\n\t\treturn GetAssetAbsPathWithOpt(relativePath, false)\n\t}\n\n\tp := filepath.Join(util.DataDir, boxID, relativePath)\n\tif gulu.File.IsExist(p) {\n\t\tif !gulu.File.IsSubPath(util.WorkspaceDir, p) {\n\t\t\treturn \"\", fmt.Errorf(\"[%s] is not sub path of workspace\", p)\n\t\t}\n\t\t// 解析符号链接/目录联接，防止软链接跳出资产根目录\n\t\tif realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {\n\t\t\tif !gulu.File.IsSubPath(util.WorkspaceDir, realP) {\n\t\t\t\treturn \"\", fmt.Errorf(\"symlink [%s] resolves outside workspace: [%s]\", p, realP)","sourceCodeStart":1066,"sourceCodeEnd":1102,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L1066-L1102","documentation":"Thrown by GetAssetAbsPathInBox (kernel/model/assets.go:1084) when the cleaned relativePath does not start with the literal `assets/` prefix. The box-scoped resolver only handles assets, so any other relative path (e.g. `data/2024.../file.sy`, `storage/x`, a bare filename) is rejected up front.","triggerScenarios":"Calling GetAssetAbsPathInBox with a relativePath that is not an asset reference — for example a notebook data file path, a `storage/` path, or an unqualified filename. Distinguish from error 407: here the path is well-formed and relative, it just isn't under `assets/`.","commonSituations":"Passing a block file path or a snippet path to the asset resolver by mistake; a frontend caller routing a non-asset URL through the asset endpoint; URL-encoded leading slash making `/assets/...` not match `assets/`.","solutions":["Confirm the value is meant to be an asset; if it is a block/snippet file, use the appropriate file API instead.","Strip a leading slash before calling so the value is `assets/...` not `/assets/...`.","Reconstruct the reference from the document's asset convention (`assets/<name>` or `<boxID>/assets/<name>`).","If you genuinely need a non-asset file, do not use GetAssetAbsPathInBox."],"exampleFix":"// before\nabs, err := model.GetAssetAbsPathInBox(\"/assets/img.png\", box)\n\n// after\nrel := strings.TrimPrefix(userInput, \"/\")\nabs, err := model.GetAssetAbsPathInBox(rel, box)","handlingStrategy":"validation","validationCode":"// Require the assets/ prefix before calling the box-scoped resolver.\nrel := strings.TrimPrefix(filepath.ToSlash(p), \"/\")\nif !strings.HasPrefix(rel, \"assets/\") {\n    return fmt.Errorf(\"not an asset path (must start with assets/): %q\", p)\n}\nreturn model.GetAssetAbsPathInBox(rel, box)","typeGuard":"func isAssetPrefixed(p string) bool {\n    return strings.HasPrefix(strings.TrimPrefix(filepath.ToSlash(p), \"/\"), \"assets/\")\n}","tryCatchPattern":null,"preventionTips":["Route only asset references through GetAssetAbsPathInBox; use the file/snippet APIs for other paths.","Strip a leading slash so `/assets/...` becomes `assets/...`.","Centralize asset-URL building in one helper to keep the prefix consistent."],"tags":["assets","validation","path-traversal","security"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}