siyuan-note/siyuan · error
path escapes templates dir: %s
Error message
path escapes templates dir: %s
What it means
`resolveTemplatePath` confines template access to the `data/templates` directory: after resolving and cleaning the path, the relative path from the templates base must not start with `..`. This prevents traversal outside the templates folder via `..` segments or an absolute path that lives elsewhere.
Source
Thrown at kernel/mcp/tools/template.go:102
sb.WriteString(fmt.Sprintf("- %s\n", r.Content))
sb.WriteString(fmt.Sprintf(" path: %s\n", r.Path))
}
return CallToolResult{Content: []ContentItem{{Type: "text", Text: sb.String()}}}, nil
}
func resolveTemplatePath(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 templateGet(args map[string]any) (CallToolResult, error) {
p, _ := args["path"].(string)
abs, err := resolveTemplatePath(p)
if err != nil {
return CallToolResult{Content: []ContentItem{{Type: "text", Text: err.Error()}}, IsError: true}, nil
}
data, err := os.ReadFile(abs)
if err != nil {
return CallToolResult{Content: []ContentItem{{Type: "text", Text: "read template failed: " + err.Error()}}, IsError: true}, nil
}
return CallToolResult{Content: []ContentItem{{Type: "text", Text: string(data)}}}, nil
}
func templateRemove(args map[string]any) (CallToolResult, error) {View on GitHub (pinned to 251596fc0d)
Solutions
- Pass a path relative to `data/templates` with no escaping `..` segments (e.g. `daily.md`).
- If using an absolute path, ensure it is genuinely inside `data/templates`.
- List templates first and reuse one of the returned paths verbatim.
Example fix
// before
{"path": "../../../conf/conf.json"}
// after
{"path": "daily.md"} Defensive patterns
Strategy: validation
Validate before calling
// Confine template paths to data/templates before invoking the tool.
func safeTemplatePath(p string) (string, error) {
if p == "" {
return "", errors.New("path is required")
}
abs := p
if !filepath.IsAbs(abs) {
abs = filepath.Join(util.DataDir, "templates", p)
}
abs = filepath.Clean(abs)
base := filepath.Clean(filepath.Join(util.DataDir, "templates"))
rel, err := filepath.Rel(base, abs)
if err != nil || strings.HasPrefix(rel, "..") || rel == ".." {
return "", fmt.Errorf("path escapes templates dir: %s", p)
}
return abs, nil
} Prevention
- Pass template paths relative to data/templates without `..` segments.
- Avoid absolute paths unless they are genuinely inside data/templates.
- List templates first and reuse a returned path.
When it happens
Trigger: Calling a template tool with a `path` containing `..` that climbs above `data/templates`, or an absolute path whose `filepath.Rel` against the templates base begins with `..`.
Common situations: Passing `../../conf/conf.json` to read a forbidden file via the template tool. Passing an absolute path (`/etc/passwd` or `/home/user/x.md`) that is not under `data/templates`. Mixing absolute and relative styles inconsistently.
Related errors
- path escapes templates dir: %s
- path escapes workspace: %s
- symlink escapes workspace: %s
- path is required
- invalid package name
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/45e01a7163b32b4e.
Report an issue: GitHub.