#1604·answer

Gravatar hash is computed from the un-lowercased email, so mixed-case accounts render an identicon

Author: Andy-Sverdlov-LucaNetCreated Sep 9, 2026Updated Sep 9, 2026

Describe the bug

pkg/gravatar.GetAvatarURL hashes the email address without lowercasing it:

go
// pkg/gravatar/gravatar.go
func GetAvatarURL(baseURL, email string) string {
	hasher := sha256.Sum256([]byte(strings.TrimSpace(email)))
	hash := hex.EncodeToString(hasher[:])
	return baseURL + hash
}

The Gravatar specification requires the address to be trimmed and lowercased before hashing, so any account whose stored address contains an uppercase letter hashes to an address Gravatar does not know. The user's avatar is never found and the identicon fallback (&d=identicon, added by the UI) is rendered instead.

Two details make this hard to work around from outside:

  1. selectedAvatar in internal/service/siteinfo_common/siteinfo_service.go recomputes the Gravatar URL from the stored address on every response, for both default_avatar: gravatar and an explicit per-user avatar.type: gravatar. Selecting "Gravatar" in Settings → Profile stores the correct URL (the UI hashes correctly, see below) but the server overwrites it on read.
  2. Nothing in the backend normalises a stored address. grep -rn ToLower internal/ | grep -i mail returns nothing, and the external-login path copies the provider's address verbatim (internal/service/user_external_login/user_external_login_service.go:258, userInfo.EMail = externalUserInfo.Email). With an OIDC/OAuth2 connector the casing is whatever the identity provider sends, so accounts are created mis-cased without any user or admin action.

The web UI already does this correctly, which is why the bug is easy to miss — ui/src/pages/Users/Settings/Profile/index.tsx:

typescript
const str = res.e_mail.toLowerCase().trim();
const hash = sha256(str);

So the avatar preview in Settings → Profile shows the user's real Gravatar while every other surface (question lists, answers, comments, user cards) shows an identicon. Frontend and backend disagree on the hash for the same account.

To Reproduce

  1. Admin → Settings → Users: set "Default avatar" to Gravatar.

  2. Create a user whose email address contains an uppercase letter, e.g. [email protected], and register that lowercase address at gravatar.com with an avatar image.

  3. Open any page listing that user (or their profile).

  4. An identicon is rendered instead of the Gravatar avatar.

  5. Confirm the cause without Answer:

    bash
    $ printf '%s' '[email protected]' | shasum -a 256   # what Gravatar expects
    $ printf '%s' '[email protected]' | shasum -a 256   # what Answer sends

    Requesting https://www.gravatar.com/avatar/<hash>?d=404 returns 200 for the first hash and 404 for the second.

  6. Settings → Profile shows the correct avatar in its preview, because the UI lowercases before hashing.

Expected behavior

GetAvatarURL lowercases the address before hashing, matching the Gravatar specification and the existing frontend behaviour, so a registered Gravatar is found regardless of the casing of the stored address.

Screenshots

n/a — the hash comparison in step 5 shows the failure directly.

Platform

  • Device: Desktop
  • OS: Linux (container), macOS client
  • Browser and version: Chrome (any)
  • Version: v2.0.2