the chain is: any room member (including federated) sets the MSC4133 profile field io.lotus.avatar_decoration to an arbitrary string → useAvatarDecoration returns it verbatim with no check against DECORATION_CATEGORIES → decorationUrl(slug) interpolates it into `${CDN}/${slug}.png` with no encodeURIComponent → pushed over io.lotus.decorations → EC's safeImageUrl accepts it because it only checks protocol === "https:" || "blob:", with no host allowlist → <img src> on every participant's tile. The origin cannot be changed (the CDN prefix is absolute), but ../../.. segments traverse to an arbitrary path on drive.lotusguild.org, and ?/# in the slug let a hostile member control the query string of a request every other caller's browser makes. Note safeImageUrl would also happily render any https origin, so the only thing preventing a third-party image beacon on every tile is that the host happens to build the URL itself.
Cross-reference: the host-side slug validation is LotusGuild/cinny#64. This issue is the EC side: safeImageUrl should pin to the expected decoration origin(s), not any https host.
How to trigger
set your own io.lotus.avatar_decoration profile field to ../../../../whatever?x=1, join a call with other Lotus users.
Suggested fix
validate the slug against the known catalog (or /^[a-z0-9_]+$/) in cinny before building the URL, and pin safeImageUrl to the expected decoration origin(s) in EC rather than accepting any https host.
Filed from the September 2026 audit (branch lotus).
**Severity:** low · **Type:** security · **Confidence:** high
**Location:** `src/lotus/lotusDecorations.ts:44-52`, `/root/code/cinny/src/app/hooks/useAvatarDecoration.ts:52-56`, `/root/code/cinny/src/app/features/lotus/avatarDecorations.ts:191-193`
### Problem
the chain is: any room member (including federated) sets the MSC4133 profile field `io.lotus.avatar_decoration` to an arbitrary string → `useAvatarDecoration` returns it verbatim with no check against `DECORATION_CATEGORIES` → `decorationUrl(slug)` interpolates it into `` `${CDN}/${slug}.png` `` with no `encodeURIComponent` → pushed over `io.lotus.decorations` → EC's `safeImageUrl` accepts it because it only checks `protocol === "https:" || "blob:"`, with no host allowlist → `<img src>` on every participant's tile. The origin cannot be changed (the CDN prefix is absolute), but `../../..` segments traverse to an arbitrary path on `drive.lotusguild.org`, and `?`/`#` in the slug let a hostile member control the query string of a request every other caller's browser makes. Note `safeImageUrl` would also happily render *any* https origin, so the only thing preventing a third-party image beacon on every tile is that the host happens to build the URL itself.
**Cross-reference:** the host-side slug validation is LotusGuild/cinny#64. This issue is the EC side: `safeImageUrl` should pin to the expected decoration origin(s), not any https host.
### How to trigger
set your own `io.lotus.avatar_decoration` profile field to `../../../../whatever?x=1`, join a call with other Lotus users.
### Suggested fix
validate the slug against the known catalog (or `/^[a-z0-9_]+$/`) in cinny before building the URL, and pin `safeImageUrl` to the expected decoration origin(s) in EC rather than accepting any https host.
---
_Filed from the September 2026 audit (branch `lotus`)._
jared
added this to the EC fork audit 2026-09 · Medium & Low milestone 2026-09-12 02:13:12 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: low · Type: security · Confidence: high
Location:
src/lotus/lotusDecorations.ts:44-52,/root/code/cinny/src/app/hooks/useAvatarDecoration.ts:52-56,/root/code/cinny/src/app/features/lotus/avatarDecorations.ts:191-193Problem
the chain is: any room member (including federated) sets the MSC4133 profile field
io.lotus.avatar_decorationto an arbitrary string →useAvatarDecorationreturns it verbatim with no check againstDECORATION_CATEGORIES→decorationUrl(slug)interpolates it into`${CDN}/${slug}.png`with noencodeURIComponent→ pushed overio.lotus.decorations→ EC'ssafeImageUrlaccepts it because it only checksprotocol === "https:" || "blob:", with no host allowlist →<img src>on every participant's tile. The origin cannot be changed (the CDN prefix is absolute), but../../..segments traverse to an arbitrary path ondrive.lotusguild.org, and?/#in the slug let a hostile member control the query string of a request every other caller's browser makes. NotesafeImageUrlwould also happily render any https origin, so the only thing preventing a third-party image beacon on every tile is that the host happens to build the URL itself.Cross-reference: the host-side slug validation is LotusGuild/cinny#64. This issue is the EC side:
safeImageUrlshould pin to the expected decoration origin(s), not any https host.How to trigger
set your own
io.lotus.avatar_decorationprofile field to../../../../whatever?x=1, join a call with other Lotus users.Suggested fix
validate the slug against the known catalog (or
/^[a-z0-9_]+$/) in cinny before building the URL, and pinsafeImageUrlto the expected decoration origin(s) in EC rather than accepting any https host.Filed from the September 2026 audit (branch
lotus).