siyuan-note/siyuan · warning
[ ] is not sub path of workspace
Error message
[%s] is not sub path of workspace
What it means
After joining <workspace>/data/<boxID>/<relativePath>, GetAssetAbsPathInBox confirms the existing path is a sub-path of the workspace directory before serving it. This error is thrown when the joined path itself lies outside the workspace — typically because DataDir or boxID were redirected, or because the joined string escaped via unusual components. It is a workspace containment guard applied before symlink evaluation.
Solutions
- Ensure the data directory physically lives under the workspace directory (DataDir must be a sub-path of WorkspaceDir)
- Fix the workspace/data configuration (workspace switcher or CLI flags) so both point at the same tree
- If using bind mounts or symlinks for notebooks, mount them inside <workspace>/data/ so containment checks pass
Example fix
// before: data dir outside workspace util.WorkspaceDir = "/home/u/ws"; util.DataDir = "/mnt/data" // after: data inside workspace util.DataDir = filepath.Join(util.WorkspaceDir, "data")
Defensive patterns
Strategy: validation
Validate before calling
p := filepath.Join(util.DataDir, boxID, rel)
if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return fmt.Errorf("path escapes workspace")
} Try / catch
abs, err := model.GetAssetAbsPathInBox(ref, boxID)
if err != nil && strings.Contains(err.Error(), "not sub path of workspace") {
// fix workspace/data configuration, then retry once
} Prevention
- Keep util.DataDir strictly inside util.WorkspaceDir (default layout)
- Do not bind-mount or symlink notebook directories into the workspace from outside the workspace tree
- In tests, set WorkspaceDir and DataDir consistently before calling model functions
When it happens
Trigger: Calling GetAssetAbsPathInBox when util.WorkspaceDir/util.DataDir are overridden (e.g. in tests or portable mode) and the constructed path resolves outside the workspace, or when a mounted volume makes the box directory not a filesystem-descendant of WorkspaceDir.
Common situations: Custom test harnesses pointing data at /tmp while WorkspaceDir stays at another root; bind-mounting notebook directories into the workspace from elsewhere; misconfigured SIYUAN_WORKING_DIR or portable-data setups.
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
- archive entry resolves outside destination
- asset escapes its directory
- asset path escapes data directory
- asset path escapes data directory
- asset path is outside data directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/353fc09ca2dfcf24.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1240
relativePath = path.Clean(relativePath)
if relativePath == "." || strings.HasPrefix(relativePath, "../") || relativePath == ".." || path.IsAbs(relativePath) {
return "", fmt.Errorf("[%s] is not an asset path", relativePath)
}
if !strings.HasPrefix(relativePath, "assets/") {
return "", fmt.Errorf("[%s] is not an asset path (must start with assets/)", relativePath)
}
if boxID != "" && !ast.IsNodeIDPattern(boxID) {
return "", fmt.Errorf("[%s] is not a box id", boxID)
}
if boxID == "" {
return GetAssetAbsPathWithOpt(relativePath, false)
}
p := filepath.Join(util.DataDir, boxID, relativePath)
if gulu.File.IsExist(p) {
if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return "", fmt.Errorf("[%s] is not sub path of workspace", p)
}
// 解析符号链接/目录联接,防止软链接跳出资产根目录
if realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {
if !gulu.File.IsSubPath(util.WorkspaceDir, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside workspace: [%s]", p, realP)
}
// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下
expectedPrefix := filepath.Join(util.DataDir, "assets")
if boxID != "" {
expectedPrefix = filepath.Join(util.DataDir, boxID, "assets")
}
if !gulu.File.IsSubPath(expectedPrefix, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside assets directory: [%s]", p, realP)
}
}
return p, nil
}
// 非加密 box 的资源可能回退到全局 data/assets(兼容旧笔记本结构)View on GitHub (pinned to 9f775e8a12)