Skip to content

fix: lowercase the email before hashing the Gravatar URL - #1605

Merged
LinkinStars merged 1 commit into
apache:devfrom
Andy-Sverdlov-LucaNet:fix/gravatar-lowercase-email
Sep 10, 2026
Merged

LinkinStars merged 1 commit into
apache:devfrom
Andy-Sverdlov-LucaNet:fix/gravatar-lowercase-email

Conversation

@Andy-Sverdlov-LucaNet

Copy link
Copy Markdown
Contributor

Fixes #1604

Proposed Changes

  • Lowercase the email address in pkg/gravatar.GetAvatarURL before hashing
    it. The Gravatar specification hashes the trimmed, lowercased address; the
    function only trimmed, so an account whose stored address contains an
    uppercase letter hashed to an address Gravatar does not know and rendered
    the identicon fallback instead of the user's avatar.
  • Add pkg/gravatar/gravatar_test.go covering the casing and whitespace
    variants of one address, all of which must produce the hash of its
    lowercase form.

selectedAvatar recomputes this URL from the stored address on every response,
so this affects both default_avatar: gravatar and an explicit per-user
avatar.type: gravatar — a user cannot work around it by re-selecting Gravatar
in their profile. The backend never normalises a stored address, and the
external-login path copies the provider's address verbatim, so accounts created
through an OIDC/OAuth2 connector inherit whatever casing the identity provider
sends.

The web UI already lowercases before hashing
(ui/src/pages/Users/Settings/Profile/index.tsx), so this also removes a
frontend/backend disagreement: the Settings → Profile preview showed the user's
real avatar while every other surface showed an identicon.

No migration is needed. The hash is computed on read, so existing accounts
resolve correctly as soon as this ships; stored addresses are left untouched.

The Gravatar specification hashes the trimmed, lowercased address.
GetAvatarURL only trimmed it, so an account whose stored address contains
an uppercase letter hashed to an address Gravatar does not know: the user's
avatar was never found and the identicon fallback was rendered instead.

selectedAvatar recomputes this URL from the stored address on every
response, so this affected both default_avatar: gravatar and an explicit
per-user avatar.type: gravatar, and a user could not work around it by
re-selecting Gravatar in their profile. Nothing in the backend normalises a
stored address, and the external login path copies the provider's address
verbatim, so accounts created through an OIDC/OAuth2 connector inherit
whatever casing the identity provider sends.

The web UI already lowercases before hashing, so the Settings -> Profile
preview showed the user's real avatar while every other surface showed an
identicon. This removes that disagreement.

The hash is computed on read, so existing accounts resolve correctly as soon
as this ships. Stored addresses are left untouched.

Co-Authored-By: Claude Opus 5 <[email protected]>
@LinkinStars
LinkinStars self-requested a review September 10, 2026 09:27
@LinkinStars LinkinStars self-assigned this Sep 10, 2026
@LinkinStars
LinkinStars changed the base branch from main to dev September 10, 2026 09:38

@LinkinStars LinkinStars left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@LinkinStars
LinkinStars merged commit 7f226dc into apache:dev Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants