Skip to content

Stop the public avatar buckets from accepting SVG files #431

Closed

Description

@HMarzban

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 = true and list image/svg+xml in allowed_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 :25 and :71 list SVG too.
  • No migration creates either bucket, so production was set up by hand. A migration must update the 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

  1. 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');.
  2. Script mirror. Remove image/svg+xml from both insert rows and both header comments in packages/supabase/scripts/12-buckets.sql. Regenerate the seed with bun run --filter @docs.plus/supabase_back seed. Never hand-edit seed.sql. No function signature changes, so types needs no run.
  3. Client. In prepareAvatarFile.ts, remove image/svg+xml from BUCKET_MIME and 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.
  4. Docs. Change packages/supabase/CLAUDE.md:48 to "jpeg/png/gif/webp only".
  5. 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-role storage.from(bucket).remove([...])). A raw SQL delete is blocked by storage.protect_delete. If a deleted object was a user's avatar.png, also clear that user's avatar_url and avatar_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_avatars bucket. Removing SVG is enough to close this issue.

Acceptance criteria

  • An upload with content type image/svg+xml to 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

  1. Apply the migration locally: docker exec -i supabase_db_docsplus_supabase psql -U postgres -d postgres < packages/supabase/migrations/<new file>.sql.
  2. Check the rows: select id, allowed_mime_types from storage.buckets where id in ('user_avatars', 'channel_avatars');.
  3. In the local webapp, upload an SVG and a PNG in Settings → Profile, then remove the avatar.
  4. cd apps/webapp && bun run typecheck.

Related


Generated by Claude Code

Activity

  1. added theissue type on Oct 6, 2026
  2. changed the title [-][Security] Public avatar buckets accept SVG, so they can host script pages[/-] [+]Stop the public avatar buckets from accepting SVG files[/+] on Oct 6, 2026
  3. HMarzban commented on Oct 9, 2026

    @HMarzban
    CollaboratorAuthor

    Fixed and live.

    • Commits: 5c2da5fde
    • Deployed in bbdaae66c (production run 37976785981, green).
    • Verified on production: migration 20261009120200 is applied, and neither avatar bucket allows image/svg+xml. No SVG avatar existed, so no clean-up was needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ProfileSecuritySecurity, access control, and data exposurebugSomething isn't working

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions