siyuan-note/siyuan · error

invalid child template path

Error message

invalid child template path

What it means

resolveTemplatePackageFile validates the relative child-template path supplied inside a template package before joining it to disk. The path is rejected when it is empty, '.', absolute, '..', or escapes upward via a '../' prefix. This prevents child templates from referencing files outside the intended resolution root.

Source

Thrown at kernel/model/template_doc_tree.go:402

		return true
	})
	if maxTemplateDocTreePlans <= count && nil != oldest {
		templateDocTreePlans.Delete(oldest.id)
	}
	templateDocTreePlans.Store(id, plan)
	templateDocTreePlansLock.Unlock()
	time.AfterFunc(templateDocTreePlanTTL, func() {
		templateDocTreePlans.Delete(id)
	})
	return collector.summary(id)
}

func resolveTemplatePackageFile(rootTemplatePath, relativePath string) (string, error) {
	relativePath = strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(relativePath)), "/")
	cleanPath := filepath.Clean(filepath.FromSlash(relativePath))
	if "" == cleanPath || "." == cleanPath || filepath.IsAbs(cleanPath) || ".." == cleanPath ||
		strings.HasPrefix(cleanPath, ".."+string(os.PathSeparator)) {
		return "", errors.New("invalid child template path")
	}
	templatesRoot := filepath.Clean(filepath.Join(util.DataDir, "templates"))
	relRootTemplate, err := filepath.Rel(templatesRoot, filepath.Clean(rootTemplatePath))
	if nil != err || strings.HasPrefix(relRootTemplate, ".."+string(os.PathSeparator)) {
		return "", errors.New("template path is outside templates directory")
	}
	parts := strings.Split(filepath.ToSlash(relRootTemplate), "/")
	packageRoot := templatesRoot
	if 1 < len(parts) {
		packageRoot = filepath.Join(templatesRoot, parts[0])
	}
	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

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Fix the child template reference in the template file to a clean relative path within the same template package (no leading '/', no '..')
  2. Ensure any template variable used in the child path renders to a non-empty relative path
  3. Replace absolute or backslash paths in the template with package-relative forward-slash paths
  4. If the child template lives in another package, move or copy it into the current package instead of escaping upward

Example fix

// before (in template content)
{{childTemplate "../shared/header.tpl"}}
// after
{{childTemplate "shared/header.tpl"}}
Defensive patterns

Strategy: validation

Validate before calling

// Go: pre-validate child template references the same way the kernel does
p := strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(ref)), "/")
clean := filepath.Clean(filepath.FromSlash(p))
invalid := clean == "" || clean == "." || clean == ".." ||
    filepath.IsAbs(clean) || strings.HasPrefix(clean, ".."+string(os.PathSeparator))

Try / catch

path, err := resolveTemplatePackageFile(root, rel)
if err != nil {
    if strings.Contains(err.Error(), "invalid child template path") {
        return fmt.Errorf("template references an illegal child path %q", rel)
    }
    return err
}

Prevention

When it happens

Trigger: Calling renderTemplateDocTreeNodeContent with a template that references a child template via a relative path that is empty, absolute (e.g. '/etc/passwd' style), '..', or starts with '../'. Also triggered directly by the test TestResolveTemplateDocTreeTemplatePathRejectsEscape with crafted escape paths.

Common situations: A template author writes {{.child "../other-template"}} to reach a sibling package; an empty path results from an unrendered template variable; a Windows-authored template uses an absolute path like 'C:\templates\x.md'; a variable used for the child path resolves to blank.

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/ae76dc3273e0c6b1. Report an issue: GitHub.