{"record":{"id":"7328361e79eb9f37","repo":"siyuan-note/siyuan","slug":"path-s-must-not-contain","errorCode":null,"errorMessage":"path [%s] must not contain '..'","messagePattern":"path \\[(.+?)\\] must not contain '\\.\\.'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/filesys/tree.go","lineNumber":161,"sourceCode":"\treturn\n}\n\n// ValidateBoxRelativePath 校验 box 内相对路径是否安全。\n// 拒绝 ..、绝对路径，确保最终路径位于 <DataDir>/<boxID> 内。\n// 允许路径以 / 开头（如 /20230101/xxx.sy），会自动标准化再去掉前导斜杠。\n// 根路径（\"/\" 或 \"\"）合法，返回空字符串。\nfunc ValidateBoxRelativePath(boxID, p string) (string, error) {\n\tp = filepath.ToSlash(p)\n\t// 记录原始路径用于 IsSubPath 校验\n\torigP := p\n\t// 标准化：去掉前导 /\n\tp = strings.TrimPrefix(p, \"/\")\n\t// 根路径直接放行（box 根目录本身是合法路径）\n\tif p == \"\" {\n\t\treturn p, nil\n\t}\n\tif strings.HasPrefix(p, \"..\") || strings.Contains(p, \"/../\") || strings.HasSuffix(p, \"/..\") || p == \"..\" || p == \".\" {\n\t\treturn \"\", fmt.Errorf(\"path [%s] must not contain '..'\", origP)\n\t}\n\tresolved := filepath.Join(util.DataDir, boxID, origP)\n\tboxRoot := filepath.Join(util.DataDir, boxID)\n\tif !gulu.File.IsSubPath(boxRoot, resolved) {\n\t\treturn \"\", fmt.Errorf(\"path [%s] escapes box directory\", origP)\n\t}\n\treturn p, nil\n}\n\nfunc LoadTreeWithFix(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, needFix bool, err error) {\n\tif _, err = ValidateBoxRelativePath(boxID, p); err != nil {\n\t\tlogging.LogErrorf(\"invalid tree path [%s] for box [%s]: %s\", p, boxID, err)\n\t\treturn\n\t}\n\n\tdek, encrypted, releaseCryptoLease, leaseErr := acquireCryptoLease(boxID)\n\tif leaseErr != nil {\n\t\terr = leaseErr","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/filesys/tree.go#L143-L179","documentation":"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.","triggerScenarios":"Calling ReadDocHPath, LoadTreeWithFix, prepareWriteTree, etc. with a path like \"../other/doc.sy\", \"a/../b.sy\", \"a/b/..\", \".\", or a raw \"..\".","commonSituations":"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.","solutions":["Remove \"..\" segments and pass a clean path rooted at the notebook, e.g. \"/folder/doc.sy\".","Resolve the path yourself against the notebook root and verify it stays inside before calling.","Sanitize/validate user input before building the path argument."],"exampleFix":"// before\nLoadTreeWithFix(\"20240101120000-abc\", \"../other/doc.sy\", lute)\n// after\nLoadTreeWithFix(\"20240101120000-abc\", \"/other/doc.sy\", lute)","handlingStrategy":"validation","validationCode":"function safeBoxPath(p) {\n  if (p.includes(\"..\") || p === \".\" || !p.startsWith(\"/\")) throw new Error(\"unsafe box path\");\n  return p;\n}","typeGuard":null,"tryCatchPattern":"if _, err := filesys.ValidateBoxRelativePath(boxID, p); err != nil {\n    // reject the request before any read/write\n}","preventionTips":["Sanitize user-supplied paths and strip \"..\" segments early.","Always express document paths relative to the notebook with a leading slash.","Add a containment check at your API boundary before calling kernel functions."],"tags":["validation","path-traversal","security","filesystem"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}