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

  1. Pass a path relative to `data/templates` with no escaping `..` segments (e.g. `daily.md`).
  2. If using an absolute path, ensure it is genuinely inside `data/templates`.
  3. 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

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


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