Skip to content

R2 object key extension is taken from the user filename instead of the validated content type #44

Description

@royalpinto007

Problem

In lib/r2/upload.ts, getPresignedUploadUrl derives the stored object key extension straight from the user supplied filename:

const ext = filename.split(".").pop() ?? "bin";
const key = `${folder}/${randomUUID()}.${ext}`;

The contentType is validated against an allowlist in app/api/upload/presign/route.ts (ALLOWED_TYPES), but the extension is not checked against it, and filename is only constrained to 1 to 255 characters. So a request with contentType: "image/png" and filename: "x.html" produces a key ending in .html, and a filename with no dot produces a key ending in the whole filename.

Why it matters

The key becomes part of a public URL under our R2 domain. The extension should describe the bytes we actually agreed to store. It is also just a correctness bug: two identical PNG uploads can end up with different extensions depending on what the browser called the file.

Suggested approach

Stop trusting the filename. Map the already validated content type to an extension:

const EXT_BY_TYPE = { "image/jpeg": "jpg", "image/png": "png", "image/webp": "webp", "image/gif": "gif" } as const;

and build the key from that. Keep the original filename only if it is useful, for example as object metadata, after sanitizing it.

Done when

  • The key extension is derived from content type, never from user input.
  • Unit tests cover each allowed content type and confirm a hostile filename cannot influence the key.

Good first issue: the change is small and self contained, but read the presign route first so you understand where contentType was already validated.


If you want to take this on, comment on the issue to claim it and it will be assigned. Please keep to a maximum of 2 open claims per person at a time so other contributors get a chance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions