{"record":{"id":"a8304a6159b8b69f","repo":"siyuan-note/siyuan","slug":"root-element-is-not-svg","errorCode":null,"errorMessage":"root element is not svg","messagePattern":"root element is not svg","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/misc.go","lineNumber":377,"sourceCode":"\t\t}\n\t\ttokenCount++\n\t\tif tokenCount > maxSVGTokens {\n\t\t\treturn \"\", fmt.Errorf(\"svg contains too many tokens\")\n\t\t}\n\n\t\tswitch typed := token.(type) {\n\t\tcase xml.StartElement:\n\t\t\telementStack = append(elementStack, typed.Name)\n\t\t\tdepth++\n\t\t\tif depth > maxSVGDepth {\n\t\t\t\treturn \"\", fmt.Errorf(\"svg nesting depth exceeds %d\", maxSVGDepth)\n\t\t\t}\n\t\t\tif rootClosed {\n\t\t\t\treturn \"\", fmt.Errorf(\"svg contains multiple root elements\")\n\t\t\t}\n\t\t\tif !rootSeen {\n\t\t\t\tif !strings.EqualFold(typed.Name.Local, \"svg\") {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"root element is not svg\")\n\t\t\t\t}\n\t\t\t\trootSeen = true\n\t\t\t}\n\n\t\t\tif skipDepth > 0 {\n\t\t\t\tskipDepth++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, unsafe := unsafeSVGElements[strings.ToLower(typed.Name.Local)]; unsafe {\n\t\t\t\tskipDepth = 1\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttyped.Name = preserveXMLName(typed.Name)\n\t\t\ttyped.Attr = sanitizeSVGAttributes(typed.Attr)\n\t\t\tif err = encoder.EncodeToken(typed); err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"render svg failed: %w\", err)\n\t\t\t}","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/misc.go#L359-L395","documentation":"SanitizeSVG requires the first (root) XML element of the input to be an <svg> element. When the first StartElement token has a local name other than 'svg' (compared case-insensitively), the sanitizer refuses the input entirely because anything wrapped in a non-SVG root is not a valid standalone SVG image and could be an attempt to smuggle other content.","triggerScenarios":"Calling SanitizeSVG with input whose first element is not <svg>: an HTML fragment, a <defs> or <g> fragment pasted from another file, a prefixed root like <s:svg> whose namespace prefix the strict decoder does not resolve, or any markup before the svg tag (the first element seen must be svg).","commonSituations":"Users pasting partial SVG snippets (inner content only) into custom emoji / dynamic icon fields; templates that wrap icons in wrapper elements; documents that begin with comments/DOCTYPE are fine, but documents that begin with another element (e.g. <html> or an <img> wrapper) are not.","solutions":["Make the root element of the input a plain <svg> element (namespace http://www.w3.org/2000/svg), e.g. wrap fragments: <svg xmlns=\"http://www.w3.org/2000/svg\">...</svg>","Verify no leading non-element markup that could be mis-parsed as an element; comments/procinsts are allowed before the root","Check the namespace prefix of the root: with decoder.Strict=true an undeclared prefix makes Space!=Local=='svg'; declare xmlns:s=\"http://www.w3.org/2000/svg\" so the local name resolves correctly","Strip surrounding HTML/wrapper markup before calling SanitizeSVG"],"exampleFix":"// before\nSanitizeSVG(\"<g><rect/></g>\") // root element is not svg\n// after\nSanitizeSVG(\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\"><g><rect/></g></svg>\")","handlingStrategy":"validation","validationCode":"func looksLikeSVG(input string) bool {\n\ts := strings.TrimSpace(input)\n\tfor strings.HasPrefix(s, \"<?\") { if i := strings.Index(s, \"?>\"); i >= 0 { s = strings.TrimSpace(s[i+2:]) } else { break } }\n\treturn strings.HasPrefix(strings.ToLower(s), \"<svg\") || strings.HasPrefix(strings.ToLower(s), \"<!doctype\")\n}","typeGuard":"func isSVGRoot(input string) bool {\n\tdec := xml.NewDecoder(strings.NewReader(input))\n\tfor {\n\t\ttok, err := dec.RawToken()\n\t\tif err != nil { return false }\n\t\tswitch t := tok.(type) {\n\t\tcase xml.StartElement:\n\t\t\treturn strings.EqualFold(t.Name.Local, \"svg\")\n\t\tcase xml.Comment, xml.ProcInst, xml.Directive, xml.CharData:\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n}","tryCatchPattern":"clean, err := util.SanitizeSVG(input)\nif err != nil {\n\tif strings.Contains(err.Error(), \"root element is not svg\") {\n\t\tinput = wrapAsSVGDocument(input) // add <svg xmlns=...> wrapper\n\t\tclean, err = util.SanitizeSVG(input)\n\t}\n\tif err != nil { return fmt.Errorf(\"invalid svg icon: %w\", err) }\n}","preventionTips":["Always pass a complete SVG document whose first element is <svg>, not a fragment","Declare the SVG namespace xmlns=\"http://www.w3.org/2000/svg\" on the root","Trim whitespace and strip HTML wrappers from user pastes before sanitizing","Pre-validate the first element with a strict XML token read"],"tags":["svg","xml","validation","sanitization"],"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"}