vercel / vercel/storage

@vercel/blob: client uploads can't set `access` — `onBeforeGenerateToken` omits it, so client uploads are locked to public

Open
#1,079 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
595
Forks
101
Avg merge
2h 24m
Merged PRs (30d)
1

Description

Summary

There is no way to request private access for a client upload. handleUpload's onBeforeGenerateToken callback — the only server-side hook where a client-upload token is minted — cannot return access, so every client-uploaded blob is forced to public, even though private Blob shipped (#816).

This is a gap left over from the private-storage rollout: server put({ access: 'private' }) works, but the client-upload path (upload()handleUpload()generateClientTokenFromReadWriteToken()) was never given an access knob.

Where it's blocked (in packages/blob/src/client.ts)

onBeforeGenerateToken's return type is a Pick that omits access:

onBeforeGenerateToken: (
  pathname: string,
  clientPayload: string | null,
  multipart: boolean,
) => Promise<
  Pick<
    GenerateClientTokenOptions,
    | 'allowedContentTypes'
    | 'maximumSizeInBytes'
    | 'validUntil'
    | 'addRandomSuffix'
    | 'allowOverwrite'
    | 'cacheControlMaxAge'
    | 'ifMatch'
  > & { tokenPayload?: string | null; callbackUrl?: string }
>;

And GenerateClientTokenOptions has no access field to Pick from — it extends BlobCommandOptions (token/oidcToken/storeId/abortSignal) and BlobClientTokenConstraintOptions (size/content-types/validUntil/suffix/overwrite/cache/ifMatch). access lives only on CommonCreateBlobOptions (packages/blob/src/helpers.ts), which the token options do not extend.

Net effect: returning { access: 'private', ... } from onBeforeGenerateToken is a TypeScript error, and there is no runtime channel to carry the intended access level into the signed client token.

Reproduction
// app/api/upload/route.ts
import { handleUpload } from '@vercel/blob/client';

export async function POST(req: Request) {
  return Response.json(
    await handleUpload({
      request: req,
      body: await req.json(),
      onBeforeGenerateToken: async () => ({
        access: 'private', // ❌ TS2353: 'access' does not exist in type Pick<GenerateClientTokenOptions, ...>
        allowedContentTypes: ['application/pdf'],
      }),
    }),
  );
}

Confirmed on @vercel/blob@2.5.0 (and 2.3.3) — the Pick and GenerateClientTokenOptions shape are identical in both.

Expected

Symmetry with the server-side API: a client upload should be able to be minted as private, e.g. onBeforeGenerateToken returning { access: 'private' }, so the resulting blob is created with private access.

Proposed fix (SDK side)
  1. Add an optional access?: BlobAccessType to the client-token options (GenerateClientTokenOptions).
  2. Add 'access' to the Pick in onBeforeGenerateToken's return type.

Because generateClientTokenFromReadWriteToken already serializes all of its non-token args into the signed token payload, and handleUpload already spreads the onBeforeGenerateToken result into it, access would flow end-to-end into the token payload with no other runtime change. A PR implementing exactly this (with a unit test asserting the decoded token payload carries access, plus a changeset) is open — see below.

Open question for maintainers

Does the control-plane / api-storage side already honor an access field embedded in a vercel_blob_client_* token payload, or is additional backend work required for it to take effect? The SDK change is safe and minimal, but if the backend ignores token-level access, the type change alone would let developers express private client uploads without them actually being private — so I've flagged this explicitly rather than assume. Happy to adjust the PR (e.g. gate it, or wire it differently) based on how the backend consumes the token.

Context
  • Follow-up to #816 (private Blob storage) — that issue delivered private access for server-side operations but did not cover the client-upload token path.
  • Real-world motivation: uploading receipts/documents that contain PII/PHI directly from the browser; these should be private, but client uploads currently can only be public.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/blob/src/client.ts and packages/blob/src/helpers.ts, then trace generateClientTokenFromReadWriteToken and handleUpload. Check how client-token options are typed and how their decoded payload is tested. Done means access can be returned by onBeforeGenerateToken and is present in the signed payload, with confirmation that the backend honors it.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.