Skip to content

staticHandler does not check response.ok after CDN fetch — returns error body as file data #16186

@toolnyc

Description

@toolnyc

Bug Report

Package: @payloadcms/storage-vercel-blob@3.59.1
File: dist/staticHandler.js, lines 39-45

Description

When clientUploads: true and disablePayloadAccessControl: true, the staticHandler is invoked by addDataAndFileToRequest to fetch the uploaded file from Vercel Blob CDN so Payload can process it (generate thumbnails, etc.).

The handler calls head(fileUrl, { token }) which succeeds, then fetches the file from the CDN:

const response = await fetch(`${fileUrl}?${uploadedAtString}`, {
    headers: { 'Cache-Control': 'no-store, no-cache, must-revalidate', Pragma: 'no-cache' }
});
const blob = await response.blob();
if (!blob) {
    return new Response(null, { status: 204, statusText: 'No Content' });
}
const bodyBuffer = await blob.arrayBuffer();
return new Response(bodyBuffer, { headers, status: 200 });

The bug: There is no if (!response.ok) check. When the CDN returns 404 (or any non-200), the error page HTML is returned as a 200 response with the correct Content-Type header from head(). Payload then passes this HTML to sharp, which crashes with FileUploadError ("There was a problem while uploading the file").

Reproduction

  1. Enable clientUploads: true and disablePayloadAccessControl: true on the Vercel Blob storage adapter
  2. Upload a file with consecutive spaces in the filename (e.g. Geranium Body II 16 x 24.jpg) using client uploads
  3. The Blob API head() confirms the file exists, but the CDN returns 404 for the URL — likely due to path normalization differences with consecutive %20 sequences
  4. sharp receives HTML error page content instead of image data and throws FileUploadError

Expected Behavior

The handler should check response.ok and either retry, fall back to downloadUrl from the head() response, or return a proper error response.

Suggested Fix

const response = await fetch(`${fileUrl}?${uploadedAtString}`, { ... });
if (!response.ok) {
    // Try the downloadUrl from head() as fallback
    const fallbackResponse = await fetch(blobMetadata.downloadUrl);
    if (!fallbackResponse.ok) {
        return new Response(null, { status: response.status, statusText: 'Blob CDN fetch failed' });
    }
    const fallbackBuffer = await fallbackResponse.arrayBuffer();
    return new Response(fallbackBuffer, { headers, status: 200 });
}

Workaround

We patched this with a Payload plugin that replaces the handler post-init — intercepting the static route and adding the response.ok check with a downloadUrl fallback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions