siyuan-note/siyuan · error
template path is outside templates directory
Error message
template path is outside templates directory
What it means
openTemplatePath computes the requested path relative to the templates root and rejects any path that resolves outside it: an empty relative result (the root itself), a parent reference ('..'), or a path beginning with '../'. This enforces the template sandbox so file reads and deletes can never escape into the rest of the workspace or filesystem. Symbolic-link escapes are additionally blocked by performing I/O through an os.Root handle.
Solutions
- Pass paths relative to the templates directory (e.g. 'sub/dir/template.md'), not absolute filesystem paths
- Strip or reject any '..' segments in user-supplied paths before calling
- Migrate stored absolute paths by extracting the portion under <DataDir>/templates
- If accessing files elsewhere is intended, use the appropriate API — the template helpers are sandboxed by design
Example fix
// before
content, err := model.ReadTemplateFile("/home/user/SiYuan/data/templates/t.md")
// after
content, err := model.ReadTemplateFile("t.md") Defensive patterns
Strategy: validation
Validate before calling
func safeRel(p string) (string, error) {
base := filepath.Join(util.DataDir, "templates")
abs := p
if !filepath.IsAbs(abs) { abs = filepath.Join(base, abs) }
rel, err := filepath.Rel(base, abs)
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, "..")+string(os.PathSeparator) { return "", errors.New("outside templates") }
if strings.Contains(rel, "..") { return "", errors.New("outside templates") }
return rel, nil
} Type guard
func withinTemplates(base, p string) bool { rel, err := filepath.Rel(base, p); return err == nil && rel != "." && rel != ".." && !strings.HasPrefix(rel, "..") } Try / catch
if err != nil && strings.Contains(err.Error(), "outside templates directory") {
// convert the absolute path to a templates-relative path and retry
} Prevention
- Always store and pass templates-relative paths
- Reject any user input containing '..' segments
- Recompute relative paths after workspace relocation
- Never join absolute filesystem paths into template API arguments
When it happens
Trigger: Calling ReadTemplateFile or RemoveTemplate with an absolute path outside <DataDir>/templates (e.g. /etc/passwd or another workspace folder), or a relative path containing ../ segments that climb above the templates root; also triggered when the path normalizes exactly to the templates root ('.').
Common situations: Storing absolute paths in config that were recorded on another machine or before a workspace move; joining user input like '../../data/foo' into a template path; passing a full filesystem path returned by an older API version that now expects root-relative paths; path traversal in an automated integration.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- asset path escapes data directory
- asset path escapes data directory
- export path is outside export directory
- history path [ ] is not in workspace
- history path [ ] is not under history directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/da2d4947cd08ccc5.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/template_path.go:28
"github.com/siyuan-note/siyuan/kernel/util"
)
// openTemplatePath 将绝对或相对路径限制在模板根目录内,文件操作通过根目录句柄防止符号链接越界。
func openTemplatePath(p string) (*os.Root, string, error) {
if p == "" {
return nil, "", errors.New("path is required")
}
base, err := filepath.Abs(filepath.Join(util.DataDir, "templates"))
if err != nil {
return nil, "", err
}
abs := p
if !filepath.IsAbs(abs) {
abs = filepath.Join(base, p)
}
rel, err := filepath.Rel(base, abs)
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return nil, "", errors.New("template path is outside templates directory")
}
root, err := os.OpenRoot(base)
return root, rel, err
}
// ReadTemplateFile 在模板根目录内读取普通文件,禁止通过符号链接读取目录外的数据。
func ReadTemplateFile(p string) ([]byte, error) {
root, rel, err := openTemplatePath(p)
if err != nil {
return nil, err
}
defer root.Close()
file, err := root.Open(rel)
if err != nil {
return nil, err
}
defer file.Close()
info, err := file.Stat()View on GitHub (pinned to 9f775e8a12)