{"record":{"id":"3d6483e9c460bd66","repo":"siyuan-note/siyuan","slug":"svg-root-element-is-incomplete","errorCode":null,"errorMessage":"svg root element is incomplete","messagePattern":"svg root element is incomplete","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/misc.go","lineNumber":450,"sourceCode":"\t\t\t}\n\t\tcase xml.Comment:\n\t\t\tif skipDepth == 0 && rootSeen && !rootClosed {\n\t\t\t\tif err = encoder.EncodeToken(typed); err != nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"render svg failed: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase xml.Directive:\n\t\t\tif !isBenignSVGDoctype(string(typed)) {\n\t\t\t\treturn \"\", fmt.Errorf(\"svg directives are not allowed\")\n\t\t\t}\n\t\t\t// 良性 DOCTYPE 声明不写入输出，与 XML 声明（ProcInst）的处理方式一致，不影响浏览器渲染\n\t\tcase xml.ProcInst:\n\t\t\t// XML 声明和处理指令不影响 SVG 图像内容，输出时统一省略。\n\t\t}\n\t}\n\n\tif !rootSeen || !rootClosed || depth != 0 || skipDepth != 0 || len(elementStack) != 0 {\n\t\treturn \"\", fmt.Errorf(\"svg root element is incomplete\")\n\t}\n\tif err := encoder.Close(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"render svg failed: %w\", err)\n\t}\n\treturn buf.String(), nil\n}\n\nfunc preserveXMLName(name xml.Name) xml.Name {\n\tif name.Space != \"\" {\n\t\tname.Local = name.Space + \":\" + name.Local\n\t\tname.Space = \"\"\n\t}\n\treturn name\n}\n\n// isBenignSVGDoctype 判断 DOCTYPE 是否仅为 svg 根元素的无内部子集声明。\n// Go 的 xml.Directive 是 <! 与 > 之间的内容；内部子集 [...] 内可声明实体，存在 XXE 风险，一律拒绝。\nfunc isBenignSVGDoctype(d string) bool {","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/misc.go#L432-L468","documentation":"After consuming all tokens, SanitizeSVG requires: a root was seen, it was closed, depth returned to 0, no unsafe-subtree skip was in progress, and the element stack is empty. If any of these invariants fail — truncated input, unclosed unsafe element, or the root never closed — the input is not a complete SVG document and is rejected.","triggerScenarios":"Input truncated mid-document: '<svg><rect/>' with no closing tags; a <script> or other unsafe element opened but its close tag cut off (skipDepth never returns to 0); input consisting only of text/comments with no elements at all (rootSeen=false).","commonSituations":"Paste cut off by a size-limited input field; network transfer truncation; string processing that dropped the tail of the file; partial fragments saved as icon files.","solutions":["Provide the complete '<svg>...</svg>' document including all closing tags","Check for truncation: compare input length/hash against the source file, or verify it ends with '</svg>' (modulo trailing whitespace)","Ensure no element (especially filtered ones like <script>) is left unclosed, which leaves skipDepth non-zero","If accepting fragments, wrap them in a complete svg root and close all elements before sanitizing"],"exampleFix":"// before\nSanitizeSVG(\"<svg><rect/></svg\") // truncated -> svg root element is incomplete\n// after\nSanitizeSVG(\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\"><rect/></svg>\")","handlingStrategy":"validation","validationCode":"func svgDocComplete(input string) bool {\n\ts := strings.TrimSpace(input)\n\treturn strings.HasPrefix(strings.ToLower(s), \"<\") && strings.HasSuffix(s, \"</svg>\")\n}","typeGuard":"func isCompleteSVG(input string) bool {\n\tif !strings.Contains(strings.ToLower(input), \"<svg\") { return false }\n\tif !strings.Contains(strings.ToLower(input), \"</svg>\") { return false }\n\tdec := xml.NewDecoder(strings.NewReader(input))\n\t_, err := xmldecAll(dec) // any full-token loop that runs to io.EOF without error\n\treturn err == nil\n}","tryCatchPattern":"clean, err := util.SanitizeSVG(input)\nif err != nil && strings.Contains(err.Error(), \"incomplete\") {\n\treturn fmt.Errorf(\"svg upload appears truncated; re-upload the full file: %w\", err)\n}","preventionTips":["Verify uploads end with '</svg>' before passing to the sanitizer","Watch for size limits on input fields that may truncate large icons","Check network/file-transfer code for partial reads (compare byte counts)","Reject fragments: require a full document with every tag closed"],"tags":["svg","xml","truncated-input","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}