siyuan-note/siyuan · error
path [ ] escapes box directory
Error message
path [%s] escapes box directory
What it means
After the textual ".." check, ValidateBoxRelativePath resolves the path against util.DataDir/boxID and uses gulu.File.IsSubPath to confirm the absolute result stays inside the notebook root. The error means the path passed the string checks but still resolves outside the box directory (e.g. via symlinks or separator tricks).
Solutions
- Verify the path resolves inside the notebook's data/<boxID> directory and remove external links.
- Check the boxID is a valid notebook ID and the path is relative to that notebook.
- Remove or replace symlinks inside the notebook that point outside the workspace.
Example fix
// before ValidateBoxRelativePath(boxID, "/link-to-external/doc.sy") // link points outside the box // after // place doc.sy inside the notebook and use "/doc.sy"
Defensive patterns
Strategy: validation
Validate before calling
const resolved = require("path").resolve(boxRoot, relPath);
if (!resolved.startsWith(boxRoot + require("path").sep)) throw new Error("resolved path escapes box"); Try / catch
if _, err := filesys.ValidateBoxRelativePath(boxID, p); err != nil {
log.Warnf("path rejected: %v", err)
return
} Prevention
- Avoid symlinks inside notebook data directories that point outside.
- Validate box IDs as plain node-ID-like strings.
- Run one OS per data directory; do not share workspaces across platforms.
When it happens
Trigger: Paths that after filepath.Join/Clean escape the box root — symlinked directories inside the notebook pointing outside, boxID itself containing traversal-like content, or platform-specific path forms the string check missed.
Common situations: Notebooks containing symlinks to external folders; corrupted box IDs; running the same data directory across OSes with different path semantics.
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
- path [ ] must not contain '..
- asset path escapes data directory
- asset path escapes data directory
- export path is outside export directory
- history path [ ] is not in workspace
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/f83563aadfb981a4.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/filesys/tree.go:166
// 允许路径以 / 开头(如 /20230101/xxx.sy),会自动标准化再去掉前导斜杠。
// 根路径("/" 或 "")合法,返回空字符串。
func ValidateBoxRelativePath(boxID, p string) (string, error) {
p = filepath.ToSlash(p)
// 记录原始路径用于 IsSubPath 校验
origP := p
// 标准化:去掉前导 /
p = strings.TrimPrefix(p, "/")
// 根路径直接放行(box 根目录本身是合法路径)
if p == "" {
return p, nil
}
if strings.HasPrefix(p, "..") || strings.Contains(p, "/../") || strings.HasSuffix(p, "/..") || p == ".." || p == "." {
return "", fmt.Errorf("path [%s] must not contain '..'", origP)
}
resolved := filepath.Join(util.DataDir, boxID, origP)
boxRoot := filepath.Join(util.DataDir, boxID)
if !gulu.File.IsSubPath(boxRoot, resolved) {
return "", fmt.Errorf("path [%s] escapes box directory", origP)
}
return p, nil
}
func LoadTreeWithFix(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, needFix bool, err error) {
if _, err = ValidateBoxRelativePath(boxID, p); err != nil {
logging.LogErrorf("invalid tree path [%s] for box [%s]: %s", p, boxID, err)
return
}
dek, encrypted, releaseCryptoLease, leaseErr := acquireCryptoLease(boxID)
if leaseErr != nil {
err = leaseErr
return
}
defer releaseCryptoLease()
rootID := util.GetTreeID(p)View on GitHub (pinned to 9f775e8a12)