Files
Bohan-J 136765c83b fix(avatar): only publish avatar-class objects through the signed endpoint (MUL-5393)
Review found that being able to name a storage object was treated as
permission to publish it. `ownedStorageKey` proved only that a URL came from
this deployment's storage, and every image-shaped key was then signed — while
the avatar update endpoints accepted any raw storage URL. A caller who had
seen a private image attachment's URL could submit it as their own avatar, and
the unauthenticated endpoint would keep re-signing it indefinitely. A user
avatar propagates to every workspace that user belongs to, so the leak crossed
workspace boundaries.

Add the missing authorization rule: an object is serveable as an avatar only
when it is avatar-class — a standalone image upload not attached to an issue,
comment, chat session, chat message, or task. The check resolves the backing
attachment row from the id UploadFile embeds in the object filename, so it
needs no lookup by URL and no new index.

It is enforced on both sides. The write side rejects such a value with 403
before anything is stored; the read side re-checks per request, which is what
makes the guarantee hold for rows written before this existed and revokes the
URL if an object is later bound to a comment or chat.

Scope is the `workspaces/` namespace — the only place that can hold content
belonging to someone other than whoever is setting the avatar, covering both
uploads and channel media ingest. Keys elsewhere (the per-user standalone
namespace, or objects an operator placed in the bucket) stay usable, which
keeps the documented "an explicit avatar_url is preserved" contract intact.

Uploader identity is deliberately not part of the rule: duplicating an agent
legitimately reuses the source agent's avatar object, which a different admin
may have uploaded. Publishing someone else's unbound image would require
knowing its URL, and unbound rows appear in no listing endpoint.

Also clamp the 302's cache lifetime to half the signed URL's own TTL (0 ->
no-store). ATTACHMENT_DOWNLOAD_URL_TTL takes any positive duration, so the
fixed 60s could outlive the target it pointed at on a short-TTL deployment.

Co-authored-by: multica-agent <github@multica.ai>
2026-07-29 15:23:12 +08:00
..