siyuan-note/siyuan · error

path escapes templates dir: %s

Error message

path escapes templates dir: %s

What it means

Returned by `resolveTemplateAbs` after `filepath.Rel(templatesBase, abs)` yields a relative path starting with `..` (or equal to `..`). This is a security guard preventing path traversal: the resolved absolute path must stay inside `data/templates/`, otherwise an attacker-controlled `--path` could read or write arbitrary files.

Source

Thrown at kernel/cli/cmd/template.go:202

		return nil
	},
}

// resolveTemplateAbs 把模板路径解析为 data/templates 下的绝对路径,拒绝越界。
// 接受绝对路径或相对 data/templates 的相对路径。
func resolveTemplateAbs(p string) (string, error) {
	if p == "" {
		return "", fmt.Errorf("--path is required")
	}
	abs := p
	if !filepath.IsAbs(abs) {
		abs = filepath.Join(util.DataDir, "templates", p)
	}
	abs = filepath.Clean(abs)
	templatesBase := filepath.Clean(filepath.Join(util.DataDir, "templates"))
	rel, err := filepath.Rel(templatesBase, abs)
	if err != nil || strings.HasPrefix(rel, "..") || rel == ".." {
		return "", fmt.Errorf("path escapes templates dir: %s", p)
	}
	return abs, nil
}

func init() {
	templateGetCmd.Flags().String("path", "", "template path (absolute or relative to data/templates)")
	templateRemoveCmd.Flags().String("path", "", "template path (absolute or relative to data/templates)")
	templateRenderCmd.Flags().String("path", "", "template path (absolute or relative to data/templates)")
	templateRenderCmd.Flags().String("id", "", "block ID to render against")
	templateSaveAsCmd.Flags().String("id", "", "source document block ID")
	templateSaveAsCmd.Flags().String("name", "", "template name without extension")
	templateSaveAsCmd.Flags().Bool("overwrite", false, "overwrite if exists")
	templateCreateCmd.Flags().String("name", "", "template name without extension")
	templateCreateCmd.Flags().String("data", "", "markdown content")
	templateCreateCmd.Flags().String("file", "", "read content from file path (- for stdin)")
	templateCreateCmd.Flags().Bool("overwrite", false, "overwrite if exists")

	rootCmd.AddCommand(templateCmd)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Use a plain template name or a path strictly under `data/templates/`, e.g. `--path sub/foo.md`.
  2. If you genuinely need a file from elsewhere, copy it into `data/templates/` first, then reference it by its in-dir path.
  3. Sanitize untrusted input by stripping leading `/` and any `..` segments before passing to the CLI.

Example fix

// before
siyuan template get --path ../../shared/note.md
// after
cp ../../shared/note.md data/templates/shared-note.md && siyuan template get --path shared-note.md
Defensive patterns

Strategy: validation

Validate before calling

// Pre-sanitize before calling resolveTemplateAbs-equivalent logic:
clean := filepath.Clean(p)
rel, err := filepath.Rel(templatesBase, clean)
if err != nil || strings.HasPrefix(rel, "..") || rel == ".." {
    return fmt.Errorf("path escapes templates dir: %s", p)
}

Prevention

When it happens

Trigger: Passing `--path ../../../etc/passwd`, `--path /etc/passwd` (absolute path outside the templates dir), or any value that, after `filepath.Clean`, resolves above `data/templates`. Symlinks inside the dir are not dereferenced by this check, but lexical escapes are blocked.

Common situations: User typos with leading `../`; tooling that concatenates untrusted input into `--path`; absolute paths intended for a different working directory.

Related errors


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