siyuan-note/siyuan · error
content template path is outside templates directory
Error message
content template path is outside templates directory
What it means
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.
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.
Example fix
// before templatePath := "../../shared/template.md" // after // copy ../../shared/template.md into <data>/templates/shared/template.md first templatePath := "shared/template.md"
Defensive patterns
Strategy: validation
Validate before calling
const rel = path.posix.normalize(templatePath.replace(/^\//, ""));
if (rel.startsWith("../") || rel === "..") throw new Error("template path escapes templates directory"); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- path escapes templates dir
- archive entry resolves outside destination
- asset escapes its directory
- asset path escapes data directory
- asset path escapes data directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/cfaae308a243128e.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/template.go:1225
tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
if err = indexWriteTreeUpsertQueue(tree); nil != err {
return err
}
av.BatchUpsertBlockRel(tree.Root.ChildrenByType(ast.NodeAttributeView))
return nil
}
func resolveDocContentTemplatePath(templatePath string) (string, error) {
templatePath = strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(templatePath)), "/")
cleanPath := filepath.Clean(filepath.FromSlash(templatePath))
if "" == cleanPath || "." == cleanPath || filepath.IsAbs(cleanPath) || ".." == cleanPath ||
strings.HasPrefix(cleanPath, ".."+string(os.PathSeparator)) {
return "", errors.New("invalid content template path")
}
templateRoot := filepath.Join(util.DataDir, "templates")
absPath := filepath.Join(templateRoot, cleanPath)
if !gulu.File.IsSubPath(templateRoot, absPath) {
return "", errors.New("content template path is outside templates directory")
}
if !filelock.IsExist(absPath) {
return "", fmt.Errorf("content template [%s] not found", templatePath)
}
realRoot, err := filepath.EvalSymlinks(templateRoot)
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("content template [%s] is not a regular file", templatePath)
}
if !gulu.File.IsSubPath(realRoot, realPath) {
return "", errors.New("content template path is outside templates directory")View on GitHub (pinned to 9f775e8a12)