siyuan-note/siyuan · error
Path [ ] is not in workspace
Error message
Path [%s] is not in workspace
What it means
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.
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
Example fix
// before
form.Set("assetsDirPath", "/home/user/elsewhere/assets")
// after
rel := "assets" // or "<boxID>/assets" relative to the workspace data dir
form.Set("assetsDirPath", rel) Defensive patterns
Strategy: validation
Validate before calling
func safeAssetsDirPath(rel string) error {
abs := filepath.Join(util.DataDir, rel)
if !util.IsAbsPathInWorkspace(abs) {
return fmt.Errorf("assetsDirPath %q escapes the workspace", rel)
}
return nil
} Try / catch
result, msg, err := model.UploadAssets(req)
if err != nil && strings.Contains(err.Error(), "is not in workspace") {
// fall back to the default: clear assetsDirPath and use workspace assets/
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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/.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- asset path is sensitive
- access to sensitive workspace file is forbidden
- archive entry escapes destination
- boxID mismatch: param=
- invalid archive entry
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/4b5de3b17f9fe032.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/upload.go:374
break
}
}
}
if nil == bt {
err = errors.New(Conf.Language(71))
return
}
uploadBoxID = bt.BoxID
docDirLocalPath := filepath.Join(util.DataDir, bt.BoxID, path.Dir(bt.Path))
assetsDirPath = getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), docDirLocalPath)
}
relAssetsDirPath := "assets"
if request.AssetsDirPath != nil {
relAssetsDirPath = *request.AssetsDirPath
assetsDirPath = filepath.Join(util.DataDir, relAssetsDirPath)
if !util.IsAbsPathInWorkspace(assetsDirPath) {
err = errors.New("Path [" + assetsDirPath + "] is not in workspace")
return
}
// assetsDirPath 可能指向加密 box(调用方未传 id),反查 boxID 让文件名脱敏和内容加密生效
if pathBox := ExtractBoxIDFromAssetsPath(assetsDirPath); pathBox != "" && IsEncryptedBox(pathBox) {
uploadBoxID = pathBox
boxAssetsDir := filepath.Join(util.DataDir, pathBox, "assets")
if rel, relErr := filepath.Rel(boxAssetsDir, assetsDirPath); relErr == nil && rel != ".." &&
!strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
// 加密资源通过 box 查询参数定位,响应转换为 box 内的标准 assets 相对路径。
relAssetsDirPath = path.Join("assets", filepath.ToSlash(rel))
}
}
}
if !gulu.File.IsExist(assetsDirPath) {
if err = os.MkdirAll(assetsDirPath, 0755); err != nil {
return
}
}View on GitHub (pinned to 9f775e8a12)