{"record":{"id":"cfaae308a243128e","repo":"siyuan-note/siyuan","slug":"content-template-path-is-outside-templates-directo","errorCode":null,"errorMessage":"content template path is outside templates directory","messagePattern":"content template path is outside templates directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template.go","lineNumber":715,"sourceCode":"\t\t\tstrings.HasPrefix(key, \"custom-\") {\n\t\t\ttree.Root.SetIALAttr(key, value)\n\t\t}\n\t}\n\ttree.Root.SetIALAttr(\"updated\", util.CurrentTimeSecondsStr())\n\treturn indexWriteTreeUpsertQueue(tree)\n}\n\nfunc resolveDocContentTemplatePath(templatePath string) (string, error) {\n\ttemplatePath = strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(templatePath)), \"/\")\n\tcleanPath := filepath.Clean(filepath.FromSlash(templatePath))\n\tif \"\" == cleanPath || \".\" == cleanPath || filepath.IsAbs(cleanPath) || \"..\" == cleanPath ||\n\t\tstrings.HasPrefix(cleanPath, \"..\"+string(os.PathSeparator)) {\n\t\treturn \"\", errors.New(\"invalid content template path\")\n\t}\n\ttemplateRoot := filepath.Join(util.DataDir, \"templates\")\n\tabsPath := filepath.Join(templateRoot, cleanPath)\n\tif !gulu.File.IsSubPath(templateRoot, absPath) {\n\t\treturn \"\", errors.New(\"content template path is outside templates directory\")\n\t}\n\tif !filelock.IsExist(absPath) {\n\t\treturn \"\", fmt.Errorf(\"content template [%s] not found\", templatePath)\n\t}\n\trealRoot, err := filepath.EvalSymlinks(templateRoot)\n\tif nil != err {\n\t\treturn \"\", err\n\t}\n\trealPath, err := filepath.EvalSymlinks(absPath)\n\tif nil != err {\n\t\treturn \"\", err\n\t}\n\tinfo, err := os.Stat(realPath)\n\tif nil != err || !info.Mode().IsRegular() {\n\t\treturn \"\", fmt.Errorf(\"content template [%s] is not a regular file\", templatePath)\n\t}\n\tif !gulu.File.IsSubPath(realRoot, realPath) {\n\t\treturn \"\", errors.New(\"content template path is outside templates directory\")","sourceCodeStart":697,"sourceCodeEnd":733,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/template.go#L697-L733","documentation":"Returned by resolveDocContentTemplatePath (template.go:715) when the lexically-resolved absolute path is not a sub-path of <data>/templates/ (gulu.File.IsSubPath check fails). This catches '..' traversal that survived the initial structural guard but still escapes the templates root after filepath.Join and filepath.Clean.","triggerScenarios":"A path like 'sub/../../../other' that, after Clean+Join against util.DataDir/templates, resolves above the templates directory. Distinct from the symlink check (932) — this is the lexical/pre-symlink guard.","commonSituations":"Crafted traversal in the template path field; a path with many '..' segments; imported documents whose template path was not sanitized upstream.","solutions":["Use a simple relative filename for the content template (e.g. 'meeting.md'), no subdirectories that traverse upward.","Validate input paths before persisting them in the doc config: reject any segment equal to '..'.","If legitimate subdirectories are needed, keep them under templates/ (e.g. 'sub/tpl.md')."],"exampleFix":"// before — traversal escapes after join\nresolveDocContentTemplatePath(\"a/../../../etc/x\")\n\n// after — stays inside templates/\nresolveDocContentTemplatePath(\"a/tpl.md\")","handlingStrategy":"validation","validationCode":"import \"path/filepath\"\nimport \"gulu/file\"\n\nfunc isInsideTemplatesRoot(p string) bool {\n    root := filepath.Join(util.DataDir, \"templates\")\n    abs := filepath.Join(root, filepath.Clean(filepath.FromSlash(p)))\n    return gulu.File.IsSubPath(root, abs)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never include '..' segments in template paths.","Keep templates flat or in subdirectories that stay under templates/.","Validate IsSubPath client-side before persisting a path."],"tags":["template","path-traversal","validation","security"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}