siyuan-note/siyuan · error

child template path is outside the current template package

Error message

child template path is outside the current template package

What it means

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.

Source

Thrown at kernel/model/template_doc_tree.go:431

	}
	absPath := filepath.Join(packageRoot, cleanPath)
	if !gulu.File.IsSubPath(packageRoot, absPath) || !filelock.IsExist(absPath) {
		return "", fmt.Errorf("child template [%s] not found in the current template package", relativePath)
	}
	realRoot, err := filepath.EvalSymlinks(packageRoot)
	if nil != err {
		return "", err
	}
	realPath, err := filepath.EvalSymlinks(absPath)
	if nil != err {
		return "", err
	}
	info, err := os.Stat(realPath)
	if nil != err || !info.Mode().IsRegular() {
		return "", fmt.Errorf("child template [%s] is not a regular file", relativePath)
	}
	if !gulu.File.IsSubPath(realRoot, realPath) {
		return "", errors.New("child template path is outside the current template package")
	}
	return realPath, nil
}

func templateDocTreeDataModel(node *TemplateDocTreeNode) map[string]string {
	return map[string]string{
		"title":    node.Title,
		"id":       node.ID,
		"parentID": node.ParentID,
		"rootID":   node.RootID,
		"hPath":    node.HPath,
		"name":     "",
		"alias":    "",
	}
}

// AttachTemplateDocTreePlans 将一次性计划转换为内核事务操作,父文档内容与全部子文档共用一条撤销记录。
func AttachTemplateDocTreePlans(transactions []*Transaction) (attached bool, err error) {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Replace the symlink with a real copy of the target file inside the template package
  2. Move the shared partial into the package and update references to use the local path
  3. If the shared content belongs to another package, keep each package self-contained and duplicate the partial
  4. Verify with a filesystem check that all files under data/templates/<pkg> are regular files physically located within the package

Example fix

# before: symlink escaping the package
ln -s ../../shared/header.md data/templates/mypkg/header.md
# after: physical copy inside the package
cp ../../shared/header.md data/templates/mypkg/header.md
Defensive patterns

Strategy: validation

Validate before calling

// Go: detect symlink escapes in a template package before use
filepath.Walk(pkgRoot, func(p string, info os.FileInfo, err error) error {
    if info.Mode()&os.ModeSymlink != 0 {
        real, _ := filepath.EvalSymlinks(p)
        if ok, _ := gulu.File.IsSubPath(pkgRoot, real); !ok {
            return fmt.Errorf("symlink escape: %s -> %s", p, real)
        }
    }
    return nil
})

Try / catch

path, err := resolveTemplatePackageFile(root, rel)
if err != nil && strings.Contains(err.Error(), "outside the current template package") {
    return fmt.Errorf("%q is a symlink pointing outside the package; copy the file in", rel)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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@8641553a1f (2026-09-11). Data as JSON: /api/errors/ec7d22c70c32f69d. Report an issue: GitHub.