{"record":{"id":"3cf2ee986131688d","repo":"siyuan-note/siyuan","slug":"invalid-custom-emoji-image","errorCode":null,"errorMessage":"invalid custom emoji image","messagePattern":"invalid custom emoji image","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":383,"sourceCode":"\n\traster := true\n\tswitch http.DetectContentType(data) {\n\tcase \"image/png\":\n\t\text = \".png\"\n\tcase \"image/jpeg\":\n\t\text = \".jpg\"\n\tcase \"image/gif\":\n\t\text = \".gif\"\n\tcase \"image/webp\":\n\t\text = \".webp\"\n\tdefault:\n\t\traster = false\n\t}\n\tif raster {\n\t\tconfig, _, decodeErr := image.DecodeConfig(bytes.NewReader(data))\n\t\tif decodeErr != nil || config.Width < 1 || config.Height < 1 || config.Width > 16384 || config.Height > 16384 ||\n\t\t\tint64(config.Width)*int64(config.Height) > 100*1000*1000 {\n\t\t\treturn nil, \"\", fmt.Errorf(\"invalid custom emoji image\")\n\t\t}\n\t\treturn data, ext, nil\n\t}\n\n\tsanitizedSVG, sanitizeErr := util.SanitizeSVG(string(data))\n\tif sanitizeErr == nil {\n\t\treturn []byte(sanitizedSVG), \".svg\", nil\n\t}\n\treturn nil, \"\", fmt.Errorf(\"unsupported custom emoji image format\")\n}\n\nfunc normalizeCustomEmojiPath(name, ext string) (string, error) {\n\tname = strings.TrimSpace(strings.ReplaceAll(name, \"\\\\\", \"/\"))\n\tparts := strings.Split(name, \"/\")\n\tif len(parts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"custom emoji name must not be empty\")\n\t}\n","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/system.go#L365-L401","documentation":"For raster images (png/jpeg/gif/webp), normalizeCustomEmojiData decodes the image configuration via image.DecodeConfig and enforces sane dimensions: width/height >= 1, <= 16384, and width*height <= 100M pixels. If decoding fails or the dimensions are out of bounds, it fails with 'invalid custom emoji image'.","triggerScenarios":"Downloading a file whose content is not a decodable raster image despite the extension (corrupted file, HTML error page), or an image with extreme dimensions (e.g. a 20000x20000 decompression-bomb PNG).","commonSituations":"URL serves an HTML login/error page with image extension; truncated download yields undecodable bytes; attacker-supplied huge-dimension image blocked as a decompression bomb.","solutions":["Verify the file is a valid image and opens in an image viewer","Re-export/resize the image to reasonable dimensions (under 16384x16384 and 100M pixels)","Re-download — the file may be truncated or corrupted","Ensure the URL serves raw image bytes, not a webpage"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const img = new Image();\nimg.onload = () => {\n  if (img.width < 1 || img.width > 16384 || img.height < 1 || img.height > 16384)\n    throw new Error(\"emoji dimensions out of range\");\n};\nimg.src = url;","typeGuard":null,"tryCatchPattern":"try {\n  await registerEmoji(data);\n} catch (e) {\n  if (String(e).includes(\"invalid custom emoji image\")) {\n    notifyUser(\"File is not a decodable image or its dimensions are too extreme\");\n  }\n}","preventionTips":["Open the image locally to confirm it decodes","Keep dimensions under 16384x16384 and 100M pixels total","Re-download truncated/corrupt files","Never point emoji URLs at HTML pages"],"tags":["image","validation","security"],"backgroundTag":"invalid-argument-value","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}