Avatar-decoration slug from a remote profile is interpolated into a URL unvalidated #64

Closed
opened 2026-09-12 01:51:22 -04:00 by jared · 0 comments
Owner

Severity: low · Type: security · Confidence: high

Location: src/app/hooks/useAvatarDecoration.ts:52-56, src/app/features/lotus/avatarDecorations.ts:191-193, src/app/components/avatar-decoration/AvatarDecoration.tsx:45, src/app/features/lotus/LotusDecorationPusher.tsx:25

Problem

io.lotus.avatar_decoration is a free-form MSC4133 profile field controlled by the remote
user (and their homeserver). Its value is passed straight to decorationUrl(slug)
${CDN}/${slug}.png with no length limit, character filter, or membership check against
ALL_DECORATIONS. Because the CDN base is absolute the attacker cannot change the host, but they can
choose an arbitrary path, add a query string (slug = "x?u=<victim-marker>"), or supply a multi-kB
string that lands in the DOM as an <img src> for every avatar of that user — and the same
unvalidated URL is forwarded into the Element Call iframe via io.lotus.decorations.

How to trigger

set your own io.lotus.avatar_decoration profile field (via PUT /_matrix/client/v3/profile/{userId}/io.lotus.avatar_decoration) to ../../anything?probe=1; every client that renders your avatar issues that request to the decorations CDN.

Suggested fix

accept the value only when it matches a slug in ALL_DECORATIONS (or at minimum
/^[a-z0-9_]{1,32}$/), and drop it otherwise.


Filed from the September 2026 client audit (branch lotus @ 4bea4895).

**Severity:** low · **Type:** security · **Confidence:** high **Location:** `src/app/hooks/useAvatarDecoration.ts:52-56`, `src/app/features/lotus/avatarDecorations.ts:191-193`, `src/app/components/avatar-decoration/AvatarDecoration.tsx:45`, `src/app/features/lotus/LotusDecorationPusher.tsx:25` ### Problem `io.lotus.avatar_decoration` is a free-form MSC4133 profile field controlled by the remote user (and their homeserver). Its value is passed straight to `decorationUrl(slug)` → `${CDN}/${slug}.png` with no length limit, character filter, or membership check against `ALL_DECORATIONS`. Because the CDN base is absolute the attacker cannot change the host, but they can choose an arbitrary path, add a query string (`slug = "x?u=<victim-marker>"`), or supply a multi-kB string that lands in the DOM as an `<img src>` for every avatar of that user — and the same unvalidated URL is forwarded into the Element Call iframe via `io.lotus.decorations`. ### How to trigger set your own `io.lotus.avatar_decoration` profile field (via `PUT /_matrix/client/v3/profile/{userId}/io.lotus.avatar_decoration`) to `../../anything?probe=1`; every client that renders your avatar issues that request to the decorations CDN. ### Suggested fix accept the value only when it matches a slug in `ALL_DECORATIONS` (or at minimum `/^[a-z0-9_]{1,32}$/`), and drop it otherwise. --- _Filed from the September 2026 client audit (branch `lotus` @ 4bea4895)._
jared added this to the Audit 2026-09 · Medium & Low milestone 2026-09-12 01:51:22 -04:00
jared added the bugpriority: lowsecurityarea: media labels 2026-09-12 01:51:22 -04:00
jared self-assigned this 2026-09-12 01:51:22 -04:00
jared closed this issue 2026-09-12 14:59:49 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#64