Repository navigation
Stop the public avatar buckets from accepting SVG files #431
Copy link
Copy link
Closed
Labels
ProfileSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Milestone
Description
Summary
user_avatars and channel_avatars are public buckets that allow image/svg+xml. Any signed-in user can upload an SVG with a script into their own folder and share its public URL. Storage serves SVG inline, so the script runs on the Supabase project origin. docs.plus itself is not affected, because it draws avatars only as <img> or a CSS background. The impact is that anyone can host a phishing page under the project URL.
- Severity: Low
- Area: Supabase Storage buckets, Settings avatar upload
- Source: security review of 2026-10-06, finding L11
Production check (2026-10-06, read-only)
- Both buckets are
public = trueand listimage/svg+xmlinallowed_mime_types. - The number of SVG objects already stored was not checked.
Where
- Bucket rows:
packages/supabase/scripts/12-buckets.sql:26-31(user_avatars) and:72-77(channel_avatars). The header comments at:25and:71list SVG too. - No migration creates either bucket, so production was set up by hand. A migration must
updatethe rows, not insert them. - The client passes SVG straight through:
apps/webapp/src/components/settings/utils/prepareAvatarFile.ts:2(BUCKET_MIME) and:36-41. Once the bucket refuses SVG, this branch would turn every SVG pick into a Storage error. - The rule that names the allowlist:
packages/supabase/CLAUDE.md:48("jpeg/png/gif/webp/svg only"). It forbids widening the list. Narrowing it is allowed. - No app code reads or writes
channel_avatars.
Fix plan
- Migration. Add one migration that runs
update storage.buckets set allowed_mime_types = '{"image/jpeg","image/png","image/gif","image/webp"}' where id in ('user_avatars', 'channel_avatars');. - Script mirror. Remove
image/svg+xmlfrom bothinsertrows and both header comments inpackages/supabase/scripts/12-buckets.sql. Regenerate the seed withbun run --filter @docs.plus/supabase_back seed. Never hand-editseed.sql. No function signature changes, sotypesneeds no run. - Client. In
prepareAvatarFile.ts, removeimage/svg+xmlfromBUCKET_MIMEand delete the SVG pass-through branch. An SVG then takes the canvas path. Where the browser cannot decode it, the user sees the existing "format is not supported" message. - Docs. Change
packages/supabase/CLAUDE.md:48to "jpeg/png/gif/webp only". - Existing objects. In production, list SVG objects read-only:
select bucket_id, name from storage.objects where bucket_id in ('user_avatars', 'channel_avatars') and metadata->>'mimetype' = 'image/svg+xml';
Delete each one through the Storage API (dashboard or a service-rolestorage.from(bucket).remove([...])). A raw SQLdeleteis blocked bystorage.protect_delete. If a deleted object was a user'savatar.png, also clear that user'savatar_urlandavatar_updated_at.
Out of scope
- Pinning upload names to
{uid}/avatar.png. With SVG gone, other names can only hold images, which cannot run script. - Dropping the unused
channel_avatarsbucket. Removing SVG is enough to close this issue.
Acceptance criteria
- An upload with content type
image/svg+xmlto either bucket is refused. - Choosing an SVG in Settings → Profile either stores a PNG, WebP or JPEG, or shows an error toast. No SVG object is stored.
- Avatar upload, change and remove still work for PNG, JPEG, WebP and AVIF in Settings.
- The production listing query in step 5 returns no rows.
Verify
- Apply the migration locally:
docker exec -i supabase_db_docsplus_supabase psql -U postgres -d postgres < packages/supabase/migrations/<new file>.sql. - Check the rows:
select id, allowed_mime_types from storage.buckets where id in ('user_avatars', 'channel_avatars');. - In the local webapp, upload an SVG and a PNG in Settings → Profile, then remove the avatar.
cd apps/webapp && bun run typecheck.
Related
- Clear the public profile when an account is soft-deleted #427 a soft-deleted account keeps its public profile (also touches
user_avatars)
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
ProfileSecuritySecurity, access control, and data exposureSecurity, access control, and data exposurebugSomething isn't workingSomething isn't working
Activity
[-][Security] Public avatar buckets accept SVG, so they can host script pages[/-][+]Stop the public avatar buckets from accepting SVG files[/+]fix(supabase): close chat mention, media, avatar and delete leaks
docs(repo): record the 9 Oct security and email batch in the changelo…
refactor(supabase): tighten 9 Oct migration notes and drop dead checks
HMarzban commentedon Oct 9, 2026
Fixed and live.
5c2da5fdebbdaae66c(production run 37976785981, green).20261009120200is applied, and neither avatar bucket allowsimage/svg+xml. No SVG avatar existed, so no clean-up was needed.