siyuan-note/siyuan · error

template path is outside templates directory

Error message

template path is outside templates directory

What it means

Returned by RemoveTemplate (template.go:97) when the resolved absolute path is not a sub-path of <data>/templates/. This is a security guard preventing arbitrary file deletion. The check uses gulu.File.IsSubPath after filepath.Clean, comparing against the canonical templates root.

Source

Thrown at kernel/model/template.go:97

	buf.Grow(4096)
	err = tpl.Execute(buf, nil)
	if err != nil {
		return "", fmt.Errorf(Conf.Language(44), err.Error())
	}
	ret = buf.String()
	return
}

// RemoveTemplate 删除模板文件,路径必须限定在 <data>/templates/ 目录内,防止任意文件被删除
func RemoveTemplate(p string) (err error) {
	abs := p
	if !filepath.IsAbs(abs) {
		abs = filepath.Join(util.DataDir, "templates", p)
	}
	abs = filepath.Clean(abs)
	templatesRoot := filepath.Clean(filepath.Join(util.DataDir, "templates"))
	if !gulu.File.IsSubPath(templatesRoot, abs) {
		return errors.New("template path is outside templates directory")
	}
	err = filelock.Remove(abs)
	if err != nil {
		logging.LogErrorf("remove template failed: %s", err)
	}
	return
}

// getTemplateReadmePaths 返回模板包 README 的相对包根路径集合:恒含 README.md,并合并 template.json 的 readme 字段(大小写敏感)。
func getTemplateReadmePaths(templateDir string) map[string]struct{} {
	paths := map[string]struct{}{"README.md": {}}
	pkg, err := bazaar.ParsePackageJSON(filepath.Join(templateDir, "template.json"))
	if err != nil {
		return paths
	}
	for _, v := range pkg.Readme {
		v = strings.TrimSpace(v)
		if "" != v {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Ensure the path passed to RemoveTemplate is a simple relative filename under templates/ with no '..' segments.
  2. Sanitize input: reject paths containing '..' or starting with '/' before calling the API.
  3. If a legitimate template deletion fails, verify the template file actually lives directly under <data>/templates/ and not in a nested data subdirectory.

Example fix

// before — traversal escapes templates root
RemoveTemplate("../../config/conf.json")

// after — relative name within templates dir
RemoveTemplate("my-template.md")
Defensive patterns

Strategy: validation

Validate before calling

import "strings"
import "path/filepath"

func safeTemplateName(p string) bool {
    p = filepath.ToSlash(filepath.Clean(p))
    if filepath.IsAbs(p) || strings.Contains(p, "../") || strings.HasPrefix(p, "/") {
        return false
    }
    return true
}

Prevention

When it happens

Trigger: The API is called with a 'p' argument containing '..' traversal segments that escape the templates directory after cleaning, e.g. '../../etc/important_file', or an absolute path pointing outside <data>/templates/. Triggered by the remove-template HTTP/CLI endpoint when a malformed or malicious path is supplied.

Common situations: A plugin or external script calls the template-remove API with a user-supplied path that is not sanitized; a path crafted to test directory traversal; a relative path that, after filepath.Join with util.DataDir/templates, still resolves above the templates root due to leading '..'.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/dcb4a452baeec3de. Report an issue: GitHub.