{"record":{"id":"2c2f02993db84ab2","repo":"siyuan-note/siyuan","slug":"invalid-template-source-file","errorCode":null,"errorMessage":"invalid template source file","messagePattern":"invalid template source file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_manage.go","lineNumber":156,"sourceCode":"\t\t})\n\t} else {\n\t\tif info.Size() > maxTemplateSourceSize {\n\t\t\treturn \"\", errors.New(\"template source is too large\")\n\t\t}\n\t\tvar content []byte\n\t\tcontent, err = root.ReadFile(p)\n\t\th.Write(content)\n\t}\n\treturn fmt.Sprintf(\"%x\", h.Sum(nil)), err\n}\n\nfunc readTemplateSource(root *os.Root, p string) (string, error) {\n\tinfo, err := root.Stat(p)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !info.Mode().IsRegular() || info.Size() > maxTemplateSourceSize {\n\t\treturn \"\", errors.New(\"invalid template source file\")\n\t}\n\tcontent, err := root.ReadFile(p)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !utf8.Valid(content) {\n\t\treturn \"\", errors.New(\"template source is not UTF-8\")\n\t}\n\treturn string(content), nil\n}\n\n// 同目录临时文件写入完成后替换，写入失败时保留原模板。\nfunc writeTemplateSource(root *os.Root, p, content string, create bool) error {\n\tif !utf8.ValidString(content) {\n\t\treturn errors.New(\"invalid template source\")\n\t}\n\tif create {\n\t\tfile, err := root.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_manage.go#L138-L174","documentation":"readTemplateSource loads the content of an existing template for the 'read' action. Before reading it requires the path to be a regular file (not a directory, device, fifo, etc.) and no larger than 8 MiB; failing either check yields 'invalid template source file'. This guards against reading non-file objects and against buffering oversized content.","triggerScenarios":"ManageTemplateFiles with action=\"read\" where request.Path exists but is not a regular file (e.g. a directory handled before the directory branch, a fifo/socket created by another process), or a regular .md file whose size exceeds maxTemplateSourceSize (8 MiB).","commonSituations":"A directory slipped past the .md-extension check because of casing tricks or the path resolves to something other than a normal file; a sync tool replaced the template with a special file; the template grew past 8 MiB from pasted content.","solutions":["Check what is actually at the path: if it is a directory, read it via the directory branch of the read action (content is empty and only a revision is returned)","Reduce the file size below 8 MiB and retry the read","Replace the non-regular file (fifo/socket/device) with a real .md file"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const st = fs.lstatSync(absPath);\nif (!st.isFile() || st.size > 8 * 1024 * 1024) {\n  throw new Error('not a regular file or over 8 MiB — refuse to read as template');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const r = await manageTemplateFiles({ action: 'read', path: p });\n} catch (e) {\n  if (String(e.message).includes('invalid template source file')) {\n    const isDir = await manageTemplateFiles({ action: 'list' }).then(l => l.find(x => x.path === p)?.isDir);\n    if (isDir) { /* handle directory branch: content is empty, revision only */ }\n  }\n}","preventionTips":["Check the list result's isDir flag before issuing a read","Never place fifos/sockets/symlinks inside data/templates/","Enforce the 8 MiB cap client-side before reading"],"tags":["template","file-type","validation","size-limit"],"backgroundTag":"invalid-argument-value","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}