Nov 4, 2026·9 min read·3 visits
Authenticated low-privileged users can bypass collection-level access controls in Payload CMS (versions 3.25.0 to < 3.90.0 and 4.0.0-canary.0 to < 4.0.0-canary.34) by directly querying `/api/upload-instructions` when using third-party storage adapters like `@payloadcms/storage-vercel-blob`, or by injecting `overrideAccess: true` into the JSON request body.
Payload CMS contains a missing authorization vulnerability (CWE-862) in its client-side upload instructions endpoint and the `@payloadcms/storage-vercel-blob` storage adapter. Two distinct logic defects allow authenticated users lacking collection-level `create` or `update` permissions to generate signed upload tokens and upload arbitrary files directly to external cloud storage.
Payload CMS is an open-source, headless content management system and application framework built with TypeScript and Node.js. It provides extensible file management capabilities through dedicated upload collections and pluggable cloud storage adapters, such as @payloadcms/storage-vercel-blob and @payloadcms/storage-r2. To reduce server bandwidth overhead when handling large media assets, Payload supports client-side direct uploads, where the browser requests a pre-signed upload URL or client token from the CMS and transmits file bytes directly to the cloud storage provider.
In Payload CMS versions 3.25.0 prior to 3.90.0 and 4.0.0-canary.0 prior to 4.0.0-canary.34, the server-side endpoint responsible for issuing these client upload credentials (/upload-instructions) fails to enforce collection-level authorization properly. Tracked as CVE-2026-93363 (and GHSA-mc8m-rr6c-r5qr), this vulnerability is classified under CWE-862 (Missing Authorization).
The flaw stems from two independent implementation defects in packages/payload/src/uploads/endpoints/uploadInstructions.ts. First, the authorization routine was conditionally gated behind a boolean check that skipped permission evaluation whenever a custom storage adapter was configured. Second, the HTTP endpoint handler passed the unsanitized client JSON body into the internal instruction generator via object spreading, allowing callers to supply internal API flags such as overrideAccess: true.
As a result, any low-privileged authenticated user can request valid upload instructions and pre-signed tokens for restricted upload collections. The user can then upload unauthorized files directly into the underlying cloud storage bucket without possessing create or update privileges on the target collection.
The root cause of CVE-2026-93363 resides in how Payload CMS processes requests to the /upload-instructions endpoint within packages/payload/src/uploads/endpoints/uploadInstructions.ts. In Payload's architecture, internal Local API operations accept an overrideAccess boolean parameter. When overrideAccess is set to true (the default for internal server-to-server calls), Payload bypasses access control functions; when invoked via HTTP REST or GraphQL endpoints, overrideAccess is supposed to be enforced as false so that collection-level permissions are evaluated against the requesting user session (req.user).
The first defect is a conditional authorization bypass triggered by the presence of a third-party storage adapter. When a collection is configured to use a storage plugin that supports client uploads (such as @payloadcms/storage-vercel-blob), the collection registers an uploadInstructions handler object. In getUploadInstructions, the access control check was written with the condition if (!uploadInstructions && !overrideAccess). Because uploadInstructions is defined and truthy when a storage adapter is active, !uploadInstructions evaluates to false. Consequently, the entire permission validation block is skipped, and execution immediately delegates token generation to uploadInstructions.generate().
The second defect is a mass assignment / internal parameter injection flaw in uploadInstructionsEndpoint. The endpoint handler parsed the incoming HTTP request JSON body into the upload variable and passed it directly into getUploadInstructions({ ...upload, req }) using the JavaScript spread operator (...).
Because the getUploadInstructions function signature accepts overrideAccess as a property of its first argument object (defaulting to false only if undefined), spreading the raw client JSON object allowed an attacker to include "overrideAccess": true in the HTTP request body. This explicitly overwrote the default overrideAccess = false parameter, disabling access control checks even for standard staged uploads that did not use a custom storage adapter.
Examining the vulnerable implementation in packages/payload/src/uploads/endpoints/uploadInstructions.ts prior to commit 47abe9bb5c444330dbfaa9a5bc2033337932270b illustrates how both defects interact within the same file.
// VULNERABLE CODE: packages/payload/src/uploads/endpoints/uploadInstructions.ts
export async function getUploadInstructions({
overrideAccess = false,
req,
...upload
}: UploadInstructionsArgs): Promise<Response> {
const uploadInstructions = req.payload.collections[upload.collectionSlug]?.config.upload?.adapter
// Defect 1: If an adapter is present (uploadInstructions is truthy),
// this entire authorization block is skipped.
if (!uploadInstructions && !overrideAccess) {
if (!req.user) {
throw new Forbidden(req.t)
}
const collectionPermissions = (await getAccessResults({ req })).collections?.[
upload.collectionSlug
]
if (!collectionPermissions?.create && !collectionPermissions?.update) {
throw new Forbidden(req.t)
}
}
return uploadInstructions
? uploadInstructions.generate({ ...upload, overrideAccess, req })
: generateStagedUploadInstructions({ ...upload, req })
}
export const uploadInstructionsEndpoint: Endpoint = {
handler: async (req) => {
const upload = (await req.json!()) as UploadInstructionsRequest
if (!upload) {
throw new APIError('Invalid upload instructions request', 400)
}
// Defect 2: Object spread (...upload) allows client JSON to inject overrideAccess: true
return Response.json(await getUploadInstructions({ ...upload, req }))
},
method: 'post',
path: '/upload-instructions',
}In commit 47abe9bb5c444330dbfaa9a5bc2033337932270b, the maintainers resolved the vulnerability through three structural changes. First, they created a dedicated authorization helper function, assertClientUploadAccess, in packages/payload/src/uploads/assertClientUploadAccess.ts. Rather than computing global access results across all collections via getAccessResults, this helper directly evaluates the target collection's create and update access control functions using executeAccess:
// PATCHED HELPER: packages/payload/src/uploads/assertClientUploadAccess.ts
export const assertClientUploadAccess = async ({
collectionSlug,
req,
}: {
collectionSlug: string
req: PayloadRequest
}): Promise<void> => {
if (!req.user) {
throw new Forbidden(req.t)
}
const collectionConfig = req.payload.collections[collectionSlug]?.config
if (!collectionConfig) {
throw new Forbidden(req.t)
}
// Evaluate collection-level create access first
const canCreate = await executeAccess(
{ slug: collectionSlug, disableErrors: true, req },
collectionConfig.access.create,
)
if (canCreate) {
return
}
// Fall back to evaluating update access if create is denied
const canUpdate = await executeAccess(
{ slug: collectionSlug, disableErrors: true, req },
collectionConfig.access.update,
)
if (!canUpdate) {
throw new Forbidden(req.t)
}
}Second, getUploadInstructions was updated to call assertClientUploadAccess unconditionally whenever overrideAccess is false, prior to invoking either custom storage adapter handlers or staged upload handlers. Third, uploadInstructionsEndpoint was refactored to explicitly pick only the expected properties from the client payload, preventing parameter injection:
// PATCHED ENDPOINT: packages/payload/src/uploads/endpoints/uploadInstructions.ts
export async function getUploadInstructions({
overrideAccess = false,
req,
...upload
}: UploadInstructionsArgs): Promise<Response> {
// Fix 1: Unconditionally enforce access checks regardless of storage adapter presence
if (!overrideAccess) {
await assertClientUploadAccess({ collectionSlug: upload.collectionSlug, req })
}
const uploadInstructions = req.payload.collections[upload.collectionSlug]?.config.upload?.adapter
return uploadInstructions
? uploadInstructions.generate({ ...upload, overrideAccess, req })
: generateStagedUploadInstructions({ ...upload, req })
}
export const uploadInstructionsEndpoint: Endpoint = {
handler: async (req) => {
const upload = (await req.json!()) as UploadInstructionsRequest
if (!upload) {
throw new APIError('Invalid upload instructions request', 400)
}
// Fix 2: Explicit property mapping prevents client injection of overrideAccess
return Response.json(
await getUploadInstructions({
collectionSlug: upload.collectionSlug,
docPrefix: upload.docPrefix,
filename: upload.filename,
filesize: upload.filesize,
mimeType: upload.mimeType,
req,
}),
)
},
method: 'post',
path: '/upload-instructions',
}> [!NOTE]
> Fix Completeness Assessment: In addition to patching /upload-instructions, commit 47abe9bb5c444330dbfaa9a5bc2033337932270b also audited and updated multipart upload handlers in other storage adapters (such as @payloadcms/storage-r2) to invoke assertClientUploadAccess({ collectionSlug, req }). This ensures consistent access enforcement across both single-request signed URLs and multipart upload initiation endpoints.
Exploiting CVE-2026-93363 requires network access to the Payload CMS HTTP API and normally requires a valid authentication session (PR:L) for a low-privileged user account (such as a standard registered user or viewer role) that lacks create and update permissions on a target upload collection. However, if an attacker exploits the overrideAccess: true parameter injection vector, the !req.user check inside the vulnerable conditional block is also bypassed.
To execute the attack against an application using @payloadcms/storage-vercel-blob or a similar client-upload configuration, the attacker first identifies the slug of an upload-enabled collection (for example, media or restricted-documents). The attacker then sends a crafted POST request to the /api/upload-instructions endpoint.
POST /api/upload-instructions HTTP/1.1
Host: target.example.com
Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"collectionSlug": "restricted-documents",
"filename": "unauthorized-asset.pdf",
"filesize": 204800,
"mimeType": "application/pdf",
"overrideAccess": true
}When the vulnerable server receives this request, uploadInstructionsEndpoint spreads the JSON properties into getUploadInstructions. Because uploadInstructions is defined on the restricted-documents collection (and overrideAccess is injected as true), the server skips all role and permission checks.
The server subsequently invokes the storage adapter's generate function, which communicates with the cloud provider (or signs a token locally using BLOB_READ_WRITE_TOKEN) and returns the upload instructions and signed token in the HTTP response. The attacker uses the returned credentials to upload the file payload directly to the Vercel Blob store or configured cloud bucket.
The primary security impact of CVE-2026-93363 is a partial loss of integrity (I:L in CVSS v3.1, VI:L in CVSS v4.0) on the affected system's storage layer. Attackers cannot read existing sensitive files (C:N) or directly delete existing database records (A:N) through this vector alone, resulting in a CVSS v3.1 base score of 4.3 (Medium) and a CVSS v4.0 base score of 5.3 (Medium).
By bypassing collection-level access controls on upload routes, an unauthorized user can persist arbitrary files inside the application's configured cloud storage bucket (such as Vercel Blob or Cloudflare R2). Depending on how the downstream application serves or processes files from the storage bucket, this enables several secondary risks:
docPrefix values, an attacker could overwrite existing objects in the blob store.According to FIRST EPSS telemetry, CVE-2026-93363 currently holds an EPSS score of 0.00177 (0.18%), placing it in the 7.28th percentile. It is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no active in-the-wild exploitation campaigns have been recorded.
To remediate CVE-2026-93363, operators must upgrade payload and all installed @payloadcms/storage-* packages (including @payloadcms/storage-vercel-blob and @payloadcms/storage-r2) to a patched release. The vulnerability is fixed in Payload 3.90.0 for the 3.x release line and Payload 4.0.0-canary.34 for the 4.x canary line.
# Using pnpm
pnpm update payload @payloadcms/storage-vercel-blob
# Using npm
npm update payload @payloadcms/storage-vercel-blobFor environments where an immediate version upgrade is not feasible, developers can implement two workarounds to mitigate the exposure. First, if using @payloadcms/storage-vercel-blob with clientUploads enabled, explicitly define an access callback inside the clientUploads configuration object to enforce user role checks inside the adapter itself:
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
vercelBlobStorage({
collections: {
media: true,
},
clientUploads: {
// Explicitly enforce authorization at the adapter level
access: ({ collectionSlug, req }) => {
return Boolean(req.user && req.user.roles?.includes('admin'))
},
},
token: process.env.BLOB_READ_WRITE_TOKEN,
})Second, deploy Web Application Firewall (WAF) or reverse proxy rules at the ingress layer to inspect POST requests targeting /api/upload-instructions. Any request body containing the JSON key overrideAccess should be blocked immediately, as legitimate client upload requests never include internal Local API control flags.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
payload / @payloadcms/storage-vercel-blob payloadcms | >= 3.25.0, < 3.90.0 | 3.90.0 |
payload / @payloadcms/storage-vercel-blob payloadcms | >= 4.0.0-canary.0, < 4.0.0-canary.34 | 4.0.0-canary.34 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Missing Authorization (CWE-862) |
| CVSS v3.1 Score | 4.3 Medium (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N) |
| CVSS v4.0 Score | 5.3 Medium (CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N) |
| EPSS Score | 0.18% (7.28th percentile) |
| Attack Vector | Network via POST /api/upload-instructions |
| Affected Versions | 3.25.0 to < 3.90.0; 4.0.0-canary.0 to < 4.0.0-canary.34 |
| Patched Versions | 3.90.0, 4.0.0-canary.34 |
| CISA KEV Status | Not Listed |
The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVE-2026-55098 (GHSA-93cm-2v4m-c56c) is an authorization bypass and sensitive information disclosure vulnerability in Basekick-Labs Arc prior to version 26.06.2. The flaw stems from an incomplete remediation of CVE-2026-47735, which failed to restrict the full suite of DuckDB table-valued filesystem I/O functions and quoted-identifier invocations. Because Arc's query parser ignores function calls during table reference extraction, authenticated users with minimal or empty permissions can invoke functions like parquet_scan(), glob(), and read_blob() to read, enumerate, and extract telemetry data across all tenant databases residing within the shared local storage root.
CVE-2026-113787 is a stored Cross-Site Scripting (XSS) and HTML injection vulnerability affecting the odf-kit npm package from version 0.6.0 prior to 0.14.2. The HTML conversion engines (odtToHtml and odsToHtml) interpolate document-controlled styling and image attributes directly into inline style and src attributes without escaping double-quote characters. A remote attacker can supply a crafted .odt or .ods document to break out of HTML attribute boundaries and execute arbitrary JavaScript in the context of any web application rendering the converted HTML.
GHSA-m3qr-fvp4-78xj is an authentication bypass and information disclosure vulnerability in Arc, an open-source, SQL-native time-series database developed by Basekick Labs and written in Go. Due to an improper route registration sequence within the Go Fiber HTTP server initialization pipeline, the application registered the /api/v1/logs endpoint prior to mounting the global authentication middleware. Because Fiber evaluates route handlers in strict declaration order and terminates request traversal when a handler returns without invoking c.Next(), unauthenticated remote callers can query the logs endpoint and retrieve up to 10,000 buffered operational log entries even when authentication is enabled.
CVE-2026-62848 (GHSA-h26j-4vp7-x9w2) is a high-severity unsafe deserialization vulnerability in Metalama, a Roslyn-based meta-programming and code generation framework for C#. The flaw resides in the inter-process communication (IPC) channel used by Metalama within Microsoft Visual Studio. Across versions 2023.0 through 2026.1.18, the framework utilized either Newtonsoft.Json with TypeNameHandling.All or MessagePack with TypelessObjectResolver over local Windows named pipes without enforcing a strict type allow-list. Because the named pipes use predictable identifiers, any local process running under the same Windows user session can connect to the endpoint and transmit crafted serialized payloads, resulting in arbitrary Common Language Runtime (CLR) type instantiation and code execution within the host Visual Studio process.
A logic flaw in github.com/tinfoil-factory/netfoil versions 0.3.0 through 0.6.0 prevents the DNS proxy from properly decrementing Time-To-Live (TTL) values on cached DNS responses and enforcing configured maximum TTL limits. Due to an idiomatic Go programming mistake where a range loop modifies value copies of slice elements rather than the underlying slice array, netfoil continually serves DNS answers with their original static TTLs. This condition causes downstream clients and resolvers to retain stale DNS records beyond their intended expiration window and bypasses local MaxTTL administrative policies.
A cryptographic signature verification flaw in Keycloak's OAuth 2.0 JWT Authorization Grant flow (RFC 7523) and federated client authentication mechanisms allows algorithm confusion between asymmetric and symmetric signing algorithms. Authenticated clients can sign forged JWT assertions using HMAC-SHA256 (HS256) with an Identity Provider's public key bytes acting as the symmetric secret. Keycloak processes the public key material as an HMAC secret key, validating the forged assertion and issuing unauthorized access tokens for federated users.