Skip to content

fix(upload): enforce size/type limits and derive storage path server-side#4

Open
eltociear wants to merge 1 commit into
profullstack:mainfrom
eltociear:fix/upload-route-auth-and-limits
Open

fix(upload): enforce size/type limits and derive storage path server-side#4
eltociear wants to merge 1 commit into
profullstack:mainfrom
eltociear:fix/upload-route-auth-and-limits

Conversation

@eltociear

Copy link
Copy Markdown

Fixes #3.

/api/upload uses the service-role Supabase client (bypasses storage RLS) with no size cap, no content-type allowlist, and a storage path taken straight from the request body. Anonymous upload is intentional (it's how a visitor attaches a logo/screenshot while submitting a new listing), so this doesn't add auth — it just closes the resource-abuse gaps:

  • File size capped at 5MB.
  • Content-type allowlisted to actual image types (png/jpeg/webp/gif/svg) instead of trusting the client's claimed file.type.
  • Storage path is now always derived server-side (Date.now()-crypto.randomUUID().ext) instead of accepted from the client, so a direct API call can't collide with/squat another upload's key.

Small, focused diff — only app/api/upload/route.ts changed.

…side

/api/upload uses the service-role Supabase client (bypasses storage RLS),
which src/lib/supabaseAdmin.ts's own doc comment says callers must gate with
their own authorization/limits before running. This route had none: no file
size cap, no content-type allowlist (the client-reported file.type was
trusted directly as contentType), and the storage path came straight from
the request body instead of being generated server-side.

Anonymous submission is intentional here (visitors upload a logo/screenshot
while submitting a new listing, see src/views/Submit.tsx), so this doesn't
add auth — but it does close off:
- unbounded storage/egress cost from arbitrarily large or numerous uploads
  (now capped at 5MB per file)
- uploading non-image content under an attacker-chosen content-type
  (now allowlisted to png/jpeg/webp/gif/svg)
- a caller picking their own storage key, which could collide with another
  upload's path (upsert: false would then reject the legitimate one) or be
  used to write outside the flat filename pattern the frontend generates
  (path is now always server-derived: timestamp + randomUUID + extension)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated /api/upload has no size/type limits and trusts a client-supplied storage path

1 participant