{"record":{"id":"1fb5707693ba9110","repo":"siyuan-note/siyuan","slug":"asset-path-must-be-under-assets","errorCode":null,"errorMessage":"asset path must be under assets","messagePattern":"asset path must be under assets","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":869,"sourceCode":"\t})\n\treturn\n}\n\nfunc generateAssetsHistory() {\n\tassets := recentModifiedAssets()\n\tif 1 > len(assets) {\n\t\treturn\n\t}\n\tif err := createAssetsHistory(assets); err != nil {\n\t\tlogging.LogErrorf(\"generate assets history failed: %s\", err)\n\t}\n}\n\n// CreateAssetHistory 为指定资源文件创建历史快照。\nfunc CreateAssetHistory(assetPath string) (err error) {\n\tassetPath = strings.TrimPrefix(filepath.ToSlash(filepath.Clean(filepath.FromSlash(assetPath))), \"/\")\n\tif !strings.HasPrefix(assetPath, \"assets/\") {\n\t\treturn errors.New(\"asset path must be under assets\")\n\t}\n\n\tassetAbsPath := filepath.Join(util.DataDir, filepath.FromSlash(assetPath))\n\tassetsDir := filepath.Join(util.DataDir, \"assets\")\n\tif !gulu.File.IsSubPath(assetsDir, assetAbsPath) {\n\t\treturn errors.New(\"asset path must be under assets\")\n\t}\n\tinfo, statErr := os.Stat(assetAbsPath)\n\tif statErr != nil {\n\t\treturn statErr\n\t}\n\tif info.IsDir() {\n\t\treturn errors.New(\"asset path must be a file\")\n\t}\n\treturn createAssetsHistory([]string{assetAbsPath})\n}\n\nfunc createAssetsHistory(assets []string) (err error) {","sourceCodeStart":851,"sourceCodeEnd":887,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history.go#L851-L887","documentation":"Thrown by CreateAssetHistory when the cleaned assetPath does not start with the prefix \"assets/\". The function first cleans the path (filepath.Clean, ToSlash, trim leading \"/\"), then checks the prefix. This is the first line of defense ensuring only paths under the assets/ directory are accepted for history snapshotting. The check rejects paths like \"data/file.txt\", \"temp/x\", or any path not rooted under assets/.","triggerScenarios":"Calling POST /api/history/createAssetHistory (or equivalent) with an assetPath that does not begin with \"assets/\" after cleaning. For example: \"file.txt\", \"/etc/passwd\", \"data/20240101/file.sy\", or any non-asset path. The prefix check is literal string comparison after path normalization.","commonSituations":"An API client passes a full filesystem path or a SiYuan-internal path instead of the relative assets/ path; a plugin constructs the path incorrectly by omitting the \"assets/\" prefix; the user or tool references a document path (.sy) instead of an asset path.","solutions":["Ensure the assetPath argument is a relative path starting with \"assets/\" (e.g. \"assets/image-202401010000.png\").","If the caller has only a filename, prefix it with \"assets/\" before calling the API.","As an API client, validate the path starts with \"assets/\" before submitting."],"exampleFix":"// before\nawait post('/api/history/createAssetHistory', { assetPath: 'image-202401010000.png' })\n\n// after\nawait post('/api/history/createAssetHistory', { assetPath: 'assets/image-202401010000.png' })","handlingStrategy":"validation","validationCode":"// Validate the asset path starts with 'assets/' before calling the API\nfunction isValidAssetPath(p) {\n  const cleaned = path.posix.normalize(p).replace(/^\\//, '')\n  return cleaned.startsWith('assets/')\n}\nif (isValidAssetPath(assetPath)) {\n  await post('/api/history/createAssetHistory', { assetPath })\n}","typeGuard":"// Type guard: check if a string is a valid asset path\nfunction isAssetPath(p: string): boolean {\n  const cleaned = p.replace(/^\\//, '')\n  return cleaned.startsWith('assets/')\n}","tryCatchPattern":null,"preventionTips":["Always prefix asset paths with 'assets/' — the API expects relative paths from the workspace data root.","Do not pass full filesystem paths, document paths (.sy), or internal kernel paths.","Validate the path prefix in the caller before submitting."],"tags":["history","asset","validation","path-validation","security"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}