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

  1. Verify the path resolves inside the notebook's data/<boxID> directory and remove external links.
  2. Check the boxID is a valid notebook ID and the path is relative to that notebook.
  3. 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

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


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)