Jun 20, 2026·6 min read·2 visits
The @jhb.software/payload-cloudinary-plugin fails to validate client-supplied parameters passed to Cloudinary's cryptographic signing helper. Authenticated users can obtain mathematically valid HMAC-SHA1 signatures for any arbitrary payload, creating a signature oracle to overwrite files, bypass visibility rules, or trigger outbound SSRF webhooks.
The @jhb.software/payload-cloudinary-plugin exposes an endpoint that performs unvalidated cryptographic signing of Cloudinary API parameters, allowing authenticated users with minimal privileges to forge valid signatures for arbitrary actions. This flaw allows attackers to overwrite remote storage assets, execute unauthorized file uploads, alter asset visibility parameters, trigger SSRF webhooks, and perform directory traversal within Cloudinary repositories.
The module @jhb.software/payload-cloudinary-plugin integrates Cloudinary cloud storage within Payload CMS architectures. When configured with client-side direct uploads enabled (clientUploads: true), the application registers a dedicated backend route designed to handle request signing. This design optimizes payload delivery by transferring large binary media uploads directly from the client browser to Cloudinary's infrastructure, reducing CPU usage and bandwidth consumption on the primary application server.\n\nBecause direct client-side uploads require authentication, Cloudinary relies on cryptographic signatures generated with a private API secret key. The plugin mounts a server-side route designed to accept upload parameters from clients, compute the corresponding HMAC-SHA1 signature, and return it to the browser. However, the endpoint acts as an unrestricted signing oracle because it lacks input sanitization, key whitelisting, or parameter verification mechanisms.\n\nAny user possessing a valid, low-privilege authentication token for the Payload CMS instance can make requests to this signing endpoint. By supplying arbitrary parameter blocks, the client forces the server to return valid cryptographic signatures. This interaction enables unauthorized asset modification, access-control subversion, and Server-Side Request Forgery within the context of the associated Cloudinary subscription.
The cryptographic security model of Cloudinary requires that upload payloads match a specific signature generated via the API_SECRET token. The signature calculation sorts the designated keys in alphabetical order, concatenates key-value pairs with ampersands, appends the secret token, and executes an SHA-1 hashing algorithm. Because the secret key must remain hidden from client-side runtime environments, the server is tasked with acting as a trusted helper to perform signature creation.\n\nIn vulnerable versions of the plugin, specifically up to version 0.3.4, the implementation in cloudinary/src/getGenerateSignature.ts fails to validate the keys contained in the request. The application registers the HTTP POST endpoint /api/cloudinary-generate-signature and binds it to a handler that reads the JSON payload directly. It parses the object named paramsToSign provided in the HTTP request body and transfers it directly to the native Cloudinary SDK utility helper.\n\nThe vulnerable sink is found at line 55 of getGenerateSignature.ts, where the application executes cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret). Because there is no schema enforcement or restriction on the dictionary keys, the server signs any values submitted by the client. The absence of validation checks means parameter injection is trivially accomplished, breaking the security boundary between client and server.
An analysis of the source code changes between the vulnerable and patched versions reveals the exact mechanism of the flaw and its remediation. In the vulnerable codebase, the handler parses the request body and processes the parameters without checking for malicious inputs or unauthorized keys:\n\ntypescript\n// Vulnerable Implementation in getGenerateSignature.ts\nconst body = await req.json?.()\nconst signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)\n\n\nIn contrast, the patched version introduces a strict validation pattern to protect the signature generation flow. The update restricts input keys, validates the format of fields, and blocks structural traversal attempts:\n\ntypescript\n// Patched Implementation in getGenerateSignature.ts\nconst paramsToSign = body.paramsToSign as Record<string, unknown>\nconst allowedKeys = new Set(['timestamp', 'folder', 'public_id'])\nif (\n !paramsToSign ||\n Object.keys(paramsToSign).some((key) => !allowedKeys.has(key)) ||\n typeof paramsToSign.timestamp !== 'string'\n) {\n throw new Forbidden()\n}\nif (folder && paramsToSign.folder !== folder.replace(/^\/|\/$/g, '')) {\n throw new Forbidden()\n}\nif (\n typeof paramsToSign.public_id === 'string' &&\n (paramsToSign.public_id.includes('..') || paramsToSign.public_id.startsWith('/'))\n) {\n throw new Forbidden()\n}\nconst signature = cloudinary.utils.api_sign_request(paramsToSign, apiSecret)\n\n\nThe patch enforces strict security controls: first, it checks incoming parameters against an allowed whitelist of timestamp, folder, and public_id, rejecting any unexpected options. Second, it requires the timestamp to be a string value. Third, it validates the folder parameter against the plugin config, preventing directory manipulation. Fourth, it blocks path-traversal strings like .. and root-relative prefix symbols / within public_id parameters. This blocks attempts to access directories outside of the configured folder structure.
Exploiting this signature oracle requires an attacker to possess any valid, low-privilege authentication token for the target Payload CMS system. Since the default route-level authorization only validates session existence (!!req.user), an authenticated guest or low-privilege editor can access the endpoint. The attacker must also retrieve the public Cloudinary parameters, such as the api_key and cloud_name, which are exposed to the client by design in index.ts to facilitate uploads.\n\nThe attacker sends a structured HTTP POST request to /api/cloudinary-generate-signature containing their desired target parameters inside the paramsToSign property. To overwrite an existing asset, the attacker generates an exploit payload containing overwrite=true, the targeted file's public_id, and a current unix timestamp. The backend server signs this payload and returns the valid HMAC-SHA1 signature.\n\nWith the generated signature, the attacker bypasses the application server entirely and transmits the forged payload directly to Cloudinary's API. The remote storage provider validates the signature against its own cryptographic keys, accepts the request, and overwrites the specified high-value media file. The automated Python script in this report demonstrates how to perform these operations, verifying that returned signatures match the expected cryptographic format.
The security implications of an unrestricted signature oracle on a Cloudinary account are extensive. Integrity is compromised because an attacker can overwrite arbitrary media files, which can lead to web application defacement, malicious file distribution, or database corruption if media metadata is parsed dynamically. This allows attackers to replace legitimate assets with spoofed or exploit-carrying files.\n\nFurthermore, the vulnerability introduces a network-level Server-Side Request Forgery risk. An attacker can request a signature that includes the notification_url parameter, pointing to a server under their control. When Cloudinary completes an upload transaction, its backend servers will make an outbound HTTP POST request to the attacker's server, leaking internal file-processing data or metadata.\n\nAdditionally, attackers can perform directory traversal attacks and invalidate CDN caches. By combining path-traversal sequences in the folder parameter with the invalidate=true option, an attacker can delete cached media resources from global CDN edge caches. This can increase administrative costs and degrade application performance, leading to a denial of service.
To remediate this vulnerability, system administrators and developers must upgrade @jhb.software/payload-cloudinary-plugin to version 0.4.0 or higher. This update introduces the parameter validation and path-filtering controls necessary to secure the signing endpoint. After updating, verify that configuration values for default storage paths are correctly populated in the plugin's initialization block.\n\nIf an immediate package upgrade is not feasible, client-side uploads can be disabled by setting clientUploads: false in the plugin's configuration options. This forces all media uploads to process directly through the Payload CMS backend, eliminating the risk associated with the signature generation endpoint. While this increases server bandwidth and processing load, it removes the attack surface.\n\nWeb Application Firewalls can also be configured to block malicious requests targeting this vulnerability. Define rules to monitor POST requests directed to /api/cloudinary-generate-signature and block payloads containing unauthorized parameters such as overwrite, notification_url, invalidate, or directory-traversal characters in the paramsToSign block. This provides defense-in-depth while the application is being upgraded.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
@jhb.software/payload-cloudinary-plugin jhb-software | >= 0.3.0 < 0.4.0 | 0.4.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-347 (Improper Verification of Cryptographic Signature) |
| Attack Vector | Network (Unauthenticated or Low-Privilege authenticated API interaction) |
| CVSS Score | 7.1 (High) |
| Impact | Integrity Loss, Server-Side Request Forgery, Directory Traversal, CDN Invalidation |
| Exploit Status | Proof-of-Concept Available |
| KEV Status | Not Listed |
The software does not verify or incorrectly verifies the cryptographic signature for data.
A Server-Side Request Forgery (SSRF) and Bearer Token Exfiltration vulnerability exists in the @merill/lokka (Lokka) Model Context Protocol (MCP) server prior to version 2.1.2. The server constructed Azure Resource Manager request URLs by concatenating user-controlled path parameters directly into destination request strings. By injecting authority-redefinition characters, an attacker can manipulate URL parsing to execute a host-escape attack, forcing the server to send high-privilege Azure Resource Manager (ARM) Bearer tokens to an external attacker-controlled host. This allows complete administrative access to the associated Azure subscriptions.
A directory traversal and symlink following vulnerability exists in Pydantic Settings when using the NestedSecretsSettingsSource with nested subdirectory lookups enabled. An attacker capable of writing to the secrets directory can bypass size limitations, read arbitrary host files, or cause a denial-of-service condition via cyclic symlinks.
A Server-Side Request Forgery (SSRF) vulnerability exists in SurrealDB's Identity & Access Management (IAM) module prior to version 3.1.5. When configuring JSON Web Key Set (JWKS) URLs for token verification, the remote fetcher follows HTTP redirects by default without validating redirect targets against configured network capabilities. This allows high-privileged users to bypass network access limits and perform blind port scanning of internal network resources.
A local file disclosure vulnerability exists in SurrealDB's full-text search capabilities, allowing authenticated users with database EDITOR or OWNER roles to read arbitrary files from the host system filesystem. This occurs by abusing the mapper() filter inside a DEFINE ANALYZER statement to point to system files.
SurrealDB versions 3.0.0 through 3.1.4 contain an information exposure vulnerability (CWE-203) where the query planner optimizes sorted queries using indexes on fields with field-level SELECT restrictions. Because the query planner performs index-based sorting before enforcing permission-based redaction, unauthorized users can observe the physical order of returned rows to deduce the relative values of protected fields.
A security vulnerability exists in SurrealDB's streaming query planner where streaming graph edge traversals or reverse-reference traversals bypass field-level SELECT permissions. This vulnerability allows an authenticated database user with valid, low-privileged credentials holding table-level SELECT permissions to bypass field-level access controls and read highly confidential or restricted fields.