{"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":1225,"sourceCode":"\ttree.Root.SetIALAttr(\"updated\", util.CurrentTimeSecondsStr())\n\tif err = indexWriteTreeUpsertQueue(tree); nil != err {\n\t\treturn err\n\t}\n\tav.BatchUpsertBlockRel(tree.Root.ChildrenByType(ast.NodeAttributeView))\n\treturn nil\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":1207,"sourceCodeEnd":1243,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/template.go#L1207-L1243","documentation":"After cleaning the input, resolveDocContentTemplatePath joins it with <data>/templates and verifies the result is still a subpath of the templates directory. If the cleaned path escapes that root (e.g. via a symlink-unaware traversal the Clean stage missed at the logical level), the path is rejected to prevent reading templates from arbitrary locations.","triggerScenarios":"Calling applyDocContentTemplate with a path whose join escapes the templates root, such as a path that Clean could not fully normalize or a crafted subpath that IsSubPath rejects.","commonSituations":"Attempts to reference templates outside the workspace, e.g. shared template folders mounted elsewhere; integration code pointing at a central template repository by absolute or upward-relative path.","solutions":["Copy or symlink the template into <data>/templates/ and reference it by its relative name.","Use SiYuan's template sync/import features instead of referencing external directories.","Validate with filepath.Rel against the templates root before calling the API."],"exampleFix":"// before\ntemplatePath := \"../../shared/template.md\"\n// after\n// copy ../../shared/template.md into <data>/templates/shared/template.md first\ntemplatePath := \"shared/template.md\"","handlingStrategy":"validation","validationCode":"const rel = path.posix.normalize(templatePath.replace(/^\\//, \"\"));\nif (rel.startsWith(\"../\") || rel === \"..\") throw new Error(\"template path escapes templates directory\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep all templates inside <data>/templates/ and reference them by relative path","Do not symlink external template folders into the templates directory","Use SiYuan sync to distribute templates rather than shared external folders"],"tags":["path-traversal","templates","security"],"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"}