{"record":{"id":"20dbd839ed5f668f","repo":"siyuan-note/siyuan","slug":"s-is-not-an-asset-path","errorCode":null,"errorMessage":"[%s] is not an asset path","messagePattern":"\\[(.+?)\\] is not an asset path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1081,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\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// 解析符号链接/目录联接，防止软链接跳出资产根目录","sourceCodeStart":1063,"sourceCodeEnd":1099,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L1063-L1099","documentation":"Thrown by GetAssetAbsPathInBox (kernel/model/assets.go:1081) when, after path.Clean, the relativePath equals `.`, `..`, begins with `../`, or is absolute. It is the first line of path-traversal defense in the box-scoped resolver, rejecting empty or escaping input before any filesystem access.","triggerScenarios":"Calling GetAssetAbsPathInBox (the box-scoped resolver used by api/asset.go, api/file.go, api/clipboard.go, model/export.go, model/transaction.go, mcp/tools/image.go, server/serve.go:952) with input like `../etc/passwd`, `..`, an empty-after-clean string, or an absolute path such as `/data/assets/x.png`.","commonSituations":"An HTTP request (e.g. /api/asset/file or /api/file/copyFile) with a crafted path param; a plugin/MCP tool passing a user-supplied path unvalidated; URL-decoded input that contained `%2e%2e/`.","solutions":["Validate and normalize the path on the trust boundary before calling: reject anything that is absolute or contains `..`.","Ensure the input begins with `assets/` and is relative (see also error 408).","If the value came from an HTTP request, treat this as a likely attack and return 400 without retry.","For programmatic callers, build paths with filepath.Join from trusted segments rather than echoing raw input."],"exampleFix":"// before\nabs, err := model.GetAssetAbsPathInBox(userInput, box)\n\n// after — validate at the boundary\nif path.IsAbs(userInput) || strings.Contains(userInput, \"..\") {\n    return fmt.Errorf(\"invalid asset path: %q\", userInput)\n}\nabs, err := model.GetAssetAbsPathInBox(userInput, box)","handlingStrategy":"validation","validationCode":"// Reject traversal/absolute/empty input at the trust boundary.\nfunc isSafeRelativeAssetPath(p string) error {\n    c := path.Clean(filepath.ToSlash(p))\n    if c == \".\" || c == \"..\" || strings.HasPrefix(c, \"../\") || path.IsAbs(c) {\n        return fmt.Errorf(\"unsafe asset path: %q\", p)\n    }\n    return nil\n}","typeGuard":"func isRelativeAssetPath(p string) bool {\n    c := path.Clean(filepath.ToSlash(p))\n    return c != \".\" && c != \"..\" && !strings.HasPrefix(c, \"../\") && !path.IsAbs(c)\n}","tryCatchPattern":null,"preventionTips":["Validate any asset path originating from HTTP/plugin input before calling the resolver.","Reject requests containing `..` or leading slashes with HTTP 400.","Never echo raw user input into the asset path argument."],"tags":["assets","path-traversal","security","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}