{"record":{"id":"ec7d22c70c32f69d","repo":"siyuan-note/siyuan","slug":"child-template-path-is-outside-the-current-templat","errorCode":null,"errorMessage":"child template path is outside the current template package","messagePattern":"child template path is outside the current template package","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":431,"sourceCode":"\t}\n\tabsPath := filepath.Join(packageRoot, cleanPath)\n\tif !gulu.File.IsSubPath(packageRoot, absPath) || !filelock.IsExist(absPath) {\n\t\treturn \"\", fmt.Errorf(\"child template [%s] not found in the current template package\", relativePath)\n\t}\n\trealRoot, err := filepath.EvalSymlinks(packageRoot)\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(\"child template [%s] is not a regular file\", relativePath)\n\t}\n\tif !gulu.File.IsSubPath(realRoot, realPath) {\n\t\treturn \"\", errors.New(\"child template path is outside the current template package\")\n\t}\n\treturn realPath, nil\n}\n\nfunc templateDocTreeDataModel(node *TemplateDocTreeNode) map[string]string {\n\treturn map[string]string{\n\t\t\"title\":    node.Title,\n\t\t\"id\":       node.ID,\n\t\t\"parentID\": node.ParentID,\n\t\t\"rootID\":   node.RootID,\n\t\t\"hPath\":    node.HPath,\n\t\t\"name\":     \"\",\n\t\t\"alias\":    \"\",\n\t}\n}\n\n// AttachTemplateDocTreePlans 将一次性计划转换为内核事务操作，父文档内容与全部子文档共用一条撤销记录。\nfunc AttachTemplateDocTreePlans(transactions []*Transaction) (attached bool, err error) {","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L413-L449","documentation":"As the final defense, resolveTemplatePackageFile re-checks the fully resolved (symlink-followed) real path against the resolved package root with gulu.File.IsSubPath. If EvalSymlinks revealed that the file physically lives outside the template package — even though the lexical path was inside — resolution is rejected. This closes the symlink-escape hole after the earlier lexical and existence checks.","triggerScenarios":"A file inside data/templates/<pkg>/ is a symlink whose target lies outside the package (e.g. ../../other-package/shared.tpl or an absolute path elsewhere on disk), and a template references it as a child template.","commonSituations":"Users symlinking shared partials between template packages to avoid duplication; package managers that deduplicate files via symlinks; dotfile-style setups where the templates dir contains links into a personal notes repo.","solutions":["Replace the symlink with a real copy of the target file inside the template package","Move the shared partial into the package and update references to use the local path","If the shared content belongs to another package, keep each package self-contained and duplicate the partial","Verify with a filesystem check that all files under data/templates/<pkg> are regular files physically located within the package"],"exampleFix":"# before: symlink escaping the package\nln -s ../../shared/header.md data/templates/mypkg/header.md\n# after: physical copy inside the package\ncp ../../shared/header.md data/templates/mypkg/header.md","handlingStrategy":"validation","validationCode":"// Go: detect symlink escapes in a template package before use\nfilepath.Walk(pkgRoot, func(p string, info os.FileInfo, err error) error {\n    if info.Mode()&os.ModeSymlink != 0 {\n        real, _ := filepath.EvalSymlinks(p)\n        if ok, _ := gulu.File.IsSubPath(pkgRoot, real); !ok {\n            return fmt.Errorf(\"symlink escape: %s -> %s\", p, real)\n        }\n    }\n    return nil\n})","typeGuard":null,"tryCatchPattern":"path, err := resolveTemplatePackageFile(root, rel)\nif err != nil && strings.Contains(err.Error(), \"outside the current template package\") {\n    return fmt.Errorf(\"%q is a symlink pointing outside the package; copy the file in\", rel)\n}","preventionTips":["Never symlink shared partials across template packages — copy them instead","Audit packages for symlinks before installing from untrusted sources","Keep each template package fully self-contained on disk","Prefer hard copies or package-local includes when sharing common fragments"],"tags":["template","symlink","path-traversal","security","siyuan-kernel"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}