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

  1. Copy or symlink the template into <data>/templates/ and reference it by its relative name.
  2. Use SiYuan's template sync/import features instead of referencing external directories.
  3. 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

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


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)