siyuan-note/siyuan · error

invalid custom emoji name

Error message

invalid custom emoji name

What it means

Each path component of the emoji name is trimmed and checked: empty strings, '.', and '..' are rejected as 'invalid custom emoji name' before any filtering, preventing path traversal and malformed names.

Solutions

  1. Remove '.', '..', and empty segments from the name
  2. Use a single flat filename like 'emoji.png' instead of a relative path with dot segments
  3. Sanitize user input before submitting to the API
  4. Never build emoji names from filesystem paths of untrusted origin

Example fix

// before
{"name": "../../emoji.png"}
// after
{"name": "emoji.png"}
Defensive patterns

Strategy: validation

Validate before calling

const parts = name.replace(/\\/g, "/").split("/");
if (parts.some(p => ["", ".", ".."].includes(p.trim())))
  throw new Error("emoji name contains invalid path segments");

Try / catch

try {
  await registerEmoji({name});
} catch (e) {
  if (String(e).includes("invalid custom emoji name")) {
    notifyUser("Remove '.', '..', and empty segments from the name");
  }
}

Prevention

When it happens

Trigger: The emoji name contains a path component that is empty, '.', or '..' after trimming — e.g. name '../etc/passwd.png', 'a//b.png', or ' ./x.png'.

Common situations: Untrusted user input used directly as a filename; path traversal attempts; copy-paste errors leaving stray slashes or dots in the name.

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


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/7067ce79e9162b50. Report an issue: GitHub.

Appendix: source

Thrown at kernel/api/system.go:410

	return nil, "", fmt.Errorf("unsupported custom emoji image format")
}

func normalizeCustomEmojiPath(name, ext string) (string, error) {
	name = strings.TrimSpace(strings.ReplaceAll(name, "\\", "/"))
	parts := strings.Split(name, "/")
	if len(parts) == 0 {
		return "", fmt.Errorf("custom emoji name must not be empty")
	}

	lastIndex := len(parts) - 1
	switch strings.ToLower(filepath.Ext(parts[lastIndex])) {
	case ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg":
		parts[lastIndex] = strings.TrimSuffix(parts[lastIndex], filepath.Ext(parts[lastIndex]))
	}
	for i, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" || part == "." || part == ".." {
			return "", fmt.Errorf("invalid custom emoji name")
		}
		part = util.FilterUploadFileName(part)
		if part == "" || part == "." || part == ".." {
			return "", fmt.Errorf("invalid custom emoji name")
		}
		parts[i] = part
	}
	parts[lastIndex] += ext
	return strings.Join(parts, "/"), nil
}

var checkUpdate = contractHandler(apicontract.SystemCheckUpdate, func(c *gin.Context, request apicontract.SystemCheckUpdateRequest) (ret apicontract.Response[apicontract.Null]) {
	ret = apicontract.Success(apicontract.Null{})

	showMsg := request.ShowMsg
	model.CheckUpdate(showMsg)
	return
})

View on GitHub (pinned to 9f775e8a12)