{"record":{"id":"4b5de3b17f9fe032","repo":"siyuan-note/siyuan","slug":"path-s-is-not-in-workspace","errorCode":null,"errorMessage":"Path [%s] is not in workspace","messagePattern":"Path \\[(.+?)\\] is not in workspace","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/upload.go","lineNumber":374,"sourceCode":"\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif nil == bt {\n\t\t\terr = errors.New(Conf.Language(71))\n\t\t\treturn\n\t\t}\n\t\tuploadBoxID = bt.BoxID\n\t\tdocDirLocalPath := filepath.Join(util.DataDir, bt.BoxID, path.Dir(bt.Path))\n\t\tassetsDirPath = getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), docDirLocalPath)\n\t}\n\n\trelAssetsDirPath := \"assets\"\n\tif request.AssetsDirPath != nil {\n\t\trelAssetsDirPath = *request.AssetsDirPath\n\t\tassetsDirPath = filepath.Join(util.DataDir, relAssetsDirPath)\n\t\tif !util.IsAbsPathInWorkspace(assetsDirPath) {\n\t\t\terr = errors.New(\"Path [\" + assetsDirPath + \"] is not in workspace\")\n\t\t\treturn\n\t\t}\n\t\t// assetsDirPath 可能指向加密 box（调用方未传 id），反查 boxID 让文件名脱敏和内容加密生效\n\t\tif pathBox := ExtractBoxIDFromAssetsPath(assetsDirPath); pathBox != \"\" && IsEncryptedBox(pathBox) {\n\t\t\tuploadBoxID = pathBox\n\t\t\tboxAssetsDir := filepath.Join(util.DataDir, pathBox, \"assets\")\n\t\t\tif rel, relErr := filepath.Rel(boxAssetsDir, assetsDirPath); relErr == nil && rel != \"..\" &&\n\t\t\t\t!strings.HasPrefix(rel, \"..\"+string(os.PathSeparator)) {\n\t\t\t\t// 加密资源通过 box 查询参数定位，响应转换为 box 内的标准 assets 相对路径。\n\t\t\t\trelAssetsDirPath = path.Join(\"assets\", filepath.ToSlash(rel))\n\t\t\t}\n\t\t}\n\t}\n\tif !gulu.File.IsExist(assetsDirPath) {\n\t\tif err = os.MkdirAll(assetsDirPath, 0755); err != nil {\n\t\t\treturn\n\t\t}\n\t}","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/upload.go#L356-L392","documentation":"UploadAssets accepts an optional assetsDirPath telling it where to store files; the resolved absolute path must live inside the current workspace (util.IsAbsPathInWorkspace). Paths that escape the workspace — absolute paths elsewhere on disk, or relative paths whose join traverses out via '..' — are rejected with 'Path [...] is not in workspace' as a security guard against arbitrary file writes.","triggerScenarios":"POST /api/asset/upload with assetsDirPath set to an absolute path outside the workspace data dir, or a value such as '../other' whose filepath.Join(util.DataDir, ...) escapes the workspace.","commonSituations":"Scripts/plugins hard-coding an absolute destination from another machine/workspace; moving the workspace without updating a saved assetsDirPath; attempting to write directly to a sibling notebook path outside data/.","solutions":["Pass a workspace-relative assetsDirPath such as 'assets' or 'notebook-id/assets', not an absolute OS path","Remove any '..' components from assetsDirPath before sending","Recompute the path against the current workspace (util.DataDir) if the workspace was moved or reconfigured","Validate in the caller: join with the data dir and confirm the result stays prefixed by the workspace path","Omit assetsDirPath entirely to use the default assets directory"],"exampleFix":"// before\nform.Set(\"assetsDirPath\", \"/home/user/elsewhere/assets\")\n// after\nrel := \"assets\" // or \"<boxID>/assets\" relative to the workspace data dir\nform.Set(\"assetsDirPath\", rel)","handlingStrategy":"validation","validationCode":"func safeAssetsDirPath(rel string) error {\n    abs := filepath.Join(util.DataDir, rel)\n    if !util.IsAbsPathInWorkspace(abs) {\n        return fmt.Errorf(\"assetsDirPath %q escapes the workspace\", rel)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"result, msg, err := model.UploadAssets(req)\nif err != nil && strings.Contains(err.Error(), \"is not in workspace\") {\n    // fall back to the default: clear assetsDirPath and use workspace assets/\n}","preventionTips":["Send only workspace-relative assetsDirPath values ('assets', '<boxID>/assets')","Strip '..' and absolute-path components before sending","Recompute stored paths if the workspace location changes","Omit assetsDirPath when the default assets directory is acceptable"],"tags":["security","path-validation","upload","workspace"],"backgroundTag":"path-traversal-blocked","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"}