siyuan-note/siyuan · error
path [%s] escapes box directory
Error message
path [%s] escapes box directory
What it means
Returned by `filesys.ValidateBoxRelativePath` when, after lexical `..` filtering, `gulu.File.IsSubPath(boxRoot, resolved)` reports the resolved path is not inside the box directory. This catches paths that lexically look fine but resolve outside the box root (e.g. via absolute paths or filesystem semantics), closing the gap the `..` check does not cover.
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 251596fc0d)
Solutions
- Use canonical, box-relative paths only; avoid absolute paths in box-relative APIs.
- Call `filepath.Clean` and verify the result stays under the box root before invoking tree APIs.
- Audit the originating client/sync code to ensure paths are generated server-side from IDs, not from raw input.
Example fix
// before
p := "/" + untrustedPath // may resolve outside box
// after
p := filepath.Clean(untrustedPath)
if rel, err := filepath.Rel(boxRoot, filepath.Join(boxRoot, p)); err != nil || strings.HasPrefix(rel, "..") {
return errors.New("reject")
} Defensive patterns
Strategy: validation
Validate before calling
// Defense-in-depth: verify IsSubPath yourself before the call:
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)
} Prevention
- Avoid absolute paths in box-relative APIs.
- Run filepath.Clean and a Rel-based containment check at the trust boundary.
- Test path handling on all target OSes; separators and Join differ.
When it happens
Trigger: A box-relative path that, once joined with `data/<boxID>/`, lands outside `data/<boxID>` — for example an absolute path component or a value exploiting `IsSubPath` semantics on the host OS.
Common situations: Cross-platform path handling where `filepath.Join` produces an unexpected result; a client supplying a path crafted to slip past the `..` check; symlinks whose target the lexical check does not resolve.
Related errors
- path [%s] must not contain '..'
- path is not a child of assets directory: %s
- import path is not sub path of import dir
- path escapes templates dir: %s
- asset path must be relative to data directory: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/f83563aadfb981a4.
Report an issue: GitHub.