Skip to content

feat(blob): allow client uploads to set access (private client uploads)#1080

Draft
kars85 wants to merge 1 commit into
vercel:mainfrom
kars85:feat/blob-client-upload-access
Draft

feat(blob): allow client uploads to set access (private client uploads)#1080
kars85 wants to merge 1 commit into
vercel:mainfrom
kars85:feat/blob-client-upload-access

Conversation

@kars85

@kars85 kars85 commented Jul 5, 2026

Copy link
Copy Markdown

What

Lets client uploads choose their access level. handleUpload's onBeforeGenerateToken callback can now return access: 'public' | 'private', bringing client uploads to parity with the server-side put({ access }) API.

Fixes #1079.

Why

Private Blob shipped in #816, but only for server-side operations. The client-upload path (upload()handleUpload()generateClientTokenFromReadWriteToken()) was never given an access knob, so every client-uploaded blob is forced to public. onBeforeGenerateToken's return type is a Pick<GenerateClientTokenOptions, …> that omits access, and GenerateClientTokenOptions has no access member to pick from — so returning { access: 'private' } is a TypeScript error with no runtime channel to carry the intent.

Motivating use case: uploading receipts/documents containing PII/PHI directly from the browser — these should be private, but client uploads can currently only be public.

Change

Minimal and additive (packages/blob/src/client.ts):

  1. Add optional access?: BlobAccessType to GenerateClientTokenOptions.
  2. Add 'access' to the Pick in onBeforeGenerateToken's return type.

No runtime-logic change: generateClientTokenFromReadWriteToken already serializes all of its non-token args into the signed client-token payload, and handleUpload already spreads the onBeforeGenerateToken result into it — so access now flows end-to-end into the token payload automatically. When omitted, behavior is unchanged (public), so this is non-breaking.

Tests

Added to packages/blob/src/client.node.test.ts:

  • generateClientTokenFromReadWriteToken bakes access into the decoded token payload.
  • The payload omits access when it isn't provided (unchanged default).
  • handleUpload threads access from onBeforeGenerateToken into the minted client token.

Locally:

  • pnpm --filter @vercel/blob test:node → 160 passed
  • pnpm --filter @vercel/blob test:edge → passed
  • pnpm --filter @vercel/blob type-check (tsc --noEmit) → clean

Changeset included (minor).

Open question for maintainers 🔎

I could only verify the SDK side. Does the control-plane / api-storage already honor an access field embedded in a vercel_blob_client_* token payload, or is backend work needed for it to take effect? If the backend currently ignores token-level access, this type change would let developers express private client uploads without them actually being private — so I'm flagging it rather than assuming. Happy to gate it, wire it differently, or hold the SDK change until the backend consumes the token — whatever matches how you'd like this to land.


🤖 Generated with Claude Code

https://claude.ai/code/session_014yndSRPZ2WRRFJajNWX1F1

`handleUpload`'s `onBeforeGenerateToken` callback could not return `access`,
so every client-uploaded blob was forced to public even after private Blob
shipped (vercel#816). Add optional `access` to the client-token options and to the
`onBeforeGenerateToken` return `Pick`; it is serialized into the signed client
token by the existing generic token path, so no runtime-logic change is needed.
When omitted, behavior is unchanged (public).

Fixes vercel#1079

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yndSRPZ2WRRFJajNWX1F1
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

@kars85 is attempting to deploy a commit to the Curated Tests - Permanent E2E Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e963c2a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@vercel/blob Minor
vercel-storage-integration-test-suite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kars85

kars85 commented Jul 5, 2026

Copy link
Copy Markdown
Author

Answering my own open question after tracing the SDK — and it changes how this PR should be read.

Today the control-plane gets a client upload's access from the x-vercel-blob-access request header, not from the token. In packages/blob/src/put-helpers.ts the access header is set unconditionally from the client-side option:

// access is always required, so always add it to headers
headers[putOptionHeaderMap.access] = options.access; // x-vercel-blob-access

So on a client upload: upload(path, body, { access, handleUploadUrl }) → the browser sends x-vercel-blob-access: <access> (client-supplied) with the client token as auth, and api-storage reads access from that header. The vercel_blob_client_* token has never carried access.

Implication: this PR makes access expressible in onBeforeGenerateToken and bakes it into the signed token payload, but that field is inert unless api-storage is changed to read access from the token and enforce it over / instead of the client header. Without that backend half, setting access here would have no effect on the stored blob — a footgun.

I've therefore converted this to a draft. It's the SDK-side half of a two-part change; happy to keep it warm until (or adjust it to match) whatever the backend does. The security motivation is that a server-pinned, tamper-proof access (token) is stronger than a client-settable header — but that's a backend decision. Flagging so this doesn't merge as if the SDK change alone were sufficient.

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.

@vercel/blob: client uploads can't set accessonBeforeGenerateToken omits it, so client uploads are locked to public

1 participant