{"record":{"id":"45e01a7163b32b4e","repo":"siyuan-note/siyuan","slug":"path-escapes-templates-dir-s-45e01a","errorCode":null,"errorMessage":"path escapes templates dir: %s","messagePattern":"path escapes templates dir: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/template.go","lineNumber":102,"sourceCode":"\t\tsb.WriteString(fmt.Sprintf(\"- %s\\n\", r.Content))\n\t\tsb.WriteString(fmt.Sprintf(\"  path: %s\\n\", r.Path))\n\t}\n\treturn CallToolResult{Content: []ContentItem{{Type: \"text\", Text: sb.String()}}}, nil\n}\n\nfunc resolveTemplatePath(p string) (string, error) {\n\tif p == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path is required\")\n\t}\n\tabs := p\n\tif !filepath.IsAbs(abs) {\n\t\tabs = filepath.Join(util.DataDir, \"templates\", p)\n\t}\n\tabs = filepath.Clean(abs)\n\ttemplatesBase := filepath.Clean(filepath.Join(util.DataDir, \"templates\"))\n\trel, err := filepath.Rel(templatesBase, abs)\n\tif err != nil || strings.HasPrefix(rel, \"..\") || rel == \"..\" {\n\t\treturn \"\", fmt.Errorf(\"path escapes templates dir: %s\", p)\n\t}\n\treturn abs, nil\n}\n\nfunc templateGet(args map[string]any) (CallToolResult, error) {\n\tp, _ := args[\"path\"].(string)\n\tabs, err := resolveTemplatePath(p)\n\tif err != nil {\n\t\treturn CallToolResult{Content: []ContentItem{{Type: \"text\", Text: err.Error()}}, IsError: true}, nil\n\t}\n\tdata, err := os.ReadFile(abs)\n\tif err != nil {\n\t\treturn CallToolResult{Content: []ContentItem{{Type: \"text\", Text: \"read template failed: \" + err.Error()}}, IsError: true}, nil\n\t}\n\treturn CallToolResult{Content: []ContentItem{{Type: \"text\", Text: string(data)}}}, nil\n}\n\nfunc templateRemove(args map[string]any) (CallToolResult, error) {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/tools/template.go#L84-L120","documentation":"`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.","triggerScenarios":"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 `..`.","commonSituations":"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.","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."],"exampleFix":"// before\n{\"path\": \"../../../conf/conf.json\"}\n// after\n{\"path\": \"daily.md\"}","handlingStrategy":"validation","validationCode":"// Confine template paths to data/templates before invoking the tool.\nfunc safeTemplatePath(p string) (string, error) {\n    if p == \"\" {\n        return \"\", errors.New(\"path is required\")\n    }\n    abs := p\n    if !filepath.IsAbs(abs) {\n        abs = filepath.Join(util.DataDir, \"templates\", p)\n    }\n    abs = filepath.Clean(abs)\n    base := filepath.Clean(filepath.Join(util.DataDir, \"templates\"))\n    rel, err := filepath.Rel(base, abs)\n    if err != nil || strings.HasPrefix(rel, \"..\") || rel == \"..\" {\n        return \"\", fmt.Errorf(\"path escapes templates dir: %s\", p)\n    }\n    return abs, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["mcp","template","security","path-traversal","input-validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}