siyuan-note/siyuan · error

path [ ] must not contain '..

Error message

path [%s] must not contain '..'

What it means

ValidateBoxRelativePath sanitizes notebook-relative document paths. It rejects any path containing ".." (leading, embedded, trailing, or exactly ".") because such segments could escape the notebook's data directory. This is a safety check run before any tree read or write.

Solutions

  1. Remove ".." segments and pass a clean path rooted at the notebook, e.g. "/folder/doc.sy".
  2. Resolve the path yourself against the notebook root and verify it stays inside before calling.
  3. Sanitize/validate user input before building the path argument.

Example fix

// before
LoadTreeWithFix("20240101120000-abc", "../other/doc.sy", lute)
// after
LoadTreeWithFix("20240101120000-abc", "/other/doc.sy", lute)
Defensive patterns

Strategy: validation

Validate before calling

function safeBoxPath(p) {
  if (p.includes("..") || p === "." || !p.startsWith("/")) throw new Error("unsafe box path");
  return p;
}

Try / catch

if _, err := filesys.ValidateBoxRelativePath(boxID, p); err != nil {
    // reject the request before any read/write
}

Prevention

When it happens

Trigger: Calling ReadDocHPath, LoadTreeWithFix, prepareWriteTree, etc. with a path like "../other/doc.sy", "a/../b.sy", "a/b/..", ".", or a raw "..".

Common situations: User-supplied paths passed straight into the API; scripts built from untrusted input; path-joining bugs that introduce ".." segments; Windows/Unix separator handling producing normalized ".." after conversion.

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/7328361e79eb9f37. Report an issue: GitHub.

Appendix: source

Thrown at kernel/filesys/tree.go:161

	return
}

// ValidateBoxRelativePath 校验 box 内相对路径是否安全。
// 拒绝 ..、绝对路径,确保最终路径位于 <DataDir>/<boxID> 内。
// 允许路径以 / 开头(如 /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

View on GitHub (pinned to 9f775e8a12)