{"id":"EUVD-2026-63102","revision":null,"records":[{"revision":"82abd58674f2459489743dbe1e9c3db1","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-59992","aliases":[],"title":"Tina: Broken Access Control: arbitrary bucket-key write/delete in `next-tinacms-s3` (and sibling production media adapters)","description":"Tina is a headless content management system. Prior to next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4, the first-party production media adapters pass attacker-controlled object keys to storage SDK upload and delete operations without enforcing the operator's configured mediaRoot. In packages/next-tinacms-s3/src/handlers.ts, createMediaHandler accepts req.query.key for a signed PutObject URL and the DELETE path uses req.query.media as the DeleteObjectCommand key. The same missing key-boundary check exists in packages/next-tinacms-dos/src/handlers.ts, packages/next-tinacms-azure/src/handlers.ts, and packages/next-tinacms-cloudinary/src/handlers.ts. An authenticated CMS editor can therefore create or delete objects anywhere the deployment's storage credential can reach, including other tenants' or non-media objects. These issues are fixed in next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4.","state":0,"publishedAt":"2026-08-19T21:30:51.975+00:00","modifiedAt":"2026-08-21T16:48:12.641+00:00","affected":[{"ecosystem":"Vendor product","name":"tinacms","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"tinacms\",\n                    \"product\": \"tinacms\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 23.0.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"},{"ecosystem":"Vendor product","name":"next-tinacms-s3","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"tinacms\",\n                    \"product\": \"next-tinacms-s3\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 23.0.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"},{"ecosystem":"Vendor product","name":"next-tinacms-dos","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"tinacms\",\n                    \"product\": \"next-tinacms-dos\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 23.0.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"},{"ecosystem":"Vendor product","name":"next-tinacms-azure","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"tinacms\",\n                    \"product\": \"next-tinacms-azure\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 14.0.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"},{"ecosystem":"Vendor product","name":"next-tinacms-cloudinary","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"tinacms\",\n                    \"product\": \"next-tinacms-cloudinary\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 26.0.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","score":5.4,"severity":"MEDIUM"}],"references":["https://github.com/tinacms/tinacms/security/advisories/GHSA-8mq9-5fw2-5rm4","https://github.com/tinacms/tinacms/pull/7088","https://github.com/tinacms/tinacms/commit/d44558e9b4502d4f4fc2c970d22985339fe2b6ce","https://github.com/tinacms/tinacms/releases/tag/next-tinacms-s3@23.0.4"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-59992","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-59992","identifiers":["CVE-2026-59992"]},"retained":false,"historicalAffected":[]},{"revision":"a48f8859e17345699a7448581a9dfc15","observedAt":"2026-11-07T13:26:13.916+00:00","statement":{"source":"euvd","id":"EUVD-2026-63102","aliases":["CVE-2026-59992","GHSA-8mq9-5fw2-5rm4"],"title":"Tina is a headless content management system. Prior to next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4, the first-party production media adapters pass attacker-controlled object…","description":"Tina is a headless content management system. Prior to next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4, the first-party production media adapters pass attacker-controlled object keys to storage SDK upload and delete operations without enforcing the operator's configured mediaRoot. In packages/next-tinacms-s3/src/handlers.ts, createMediaHandler accepts req.query.key for a signed PutObject URL and the DELETE path uses req.query.media as the DeleteObjectCommand key. The same missing key-boundary check exists in packages/next-tinacms-dos/src/handlers.ts, packages/next-tinacms-azure/src/handlers.ts, and packages/next-tinacms-cloudinary/src/handlers.ts. An authenticated CMS editor can therefore create or delete objects anywhere the deployment's storage credential can reach, including other tenants' or non-media objects. These issues are fixed in next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","score":5.4,"severity":null}],"references":["https://github.com/tinacms/tinacms/security/advisories/GHSA-8mq9-5fw2-5rm4","https://github.com/tinacms/tinacms/pull/7088","https://github.com/tinacms/tinacms/commit/d44558e9b4502d4f4fc2c970d22985339fe2b6ce","https://github.com/tinacms/tinacms/releases/tag/next-tinacms-s3@23.0.4"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-63102","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Aug 19, 2026, 9:30:51 PM","dateUpdated":"Aug 21, 2026, 4:48:12 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-63102","identifiers":["EUVD-2026-63102","CVE-2026-59992","GHSA-8mq9-5fw2-5rm4"]},"retained":false,"historicalAffected":[]},{"revision":"d176c92a00354eb68710b06c68295269","observedAt":"2026-10-27T15:15:33.457+00:00","statement":{"source":"nvd","id":"CVE-2026-59992","aliases":[],"title":"Tina is a headless content management system. Prior to next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4, the first-party production media adapters pass attacker-controlled object…","description":"Tina is a headless content management system. Prior to next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4, the first-party production media adapters pass attacker-controlled object keys to storage SDK upload and delete operations without enforcing the operator's configured mediaRoot. In packages/next-tinacms-s3/src/handlers.ts, createMediaHandler accepts req.query.key for a signed PutObject URL and the DELETE path uses req.query.media as the DeleteObjectCommand key. The same missing key-boundary check exists in packages/next-tinacms-dos/src/handlers.ts, packages/next-tinacms-azure/src/handlers.ts, and packages/next-tinacms-cloudinary/src/handlers.ts. An authenticated CMS editor can therefore create or delete objects anywhere the deployment's storage credential can reach, including other tenants' or non-media objects. These issues are fixed in next-tinacms-s3 23.0.4, next-tinacms-dos 23.0.4, next-tinacms-azure 14.0.4, and next-tinacms-cloudinary 26.0.4.","state":0,"publishedAt":"2026-08-19T22:16:40.6+00:00","modifiedAt":"2026-10-26T10:10:00.14+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","score":5.4,"severity":"MEDIUM"}],"references":["https://github.com/tinacms/tinacms/commit/d44558e9b4502d4f4fc2c970d22985339fe2b6ce","https://github.com/tinacms/tinacms/pull/7088","https://github.com/tinacms/tinacms/releases/tag/next-tinacms-s3@23.0.4","https://github.com/tinacms/tinacms/security/advisories/GHSA-8mq9-5fw2-5rm4"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-59992","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-639","CWE-862"],"productCriteria":[],"sourceDates":{"published":"2026-08-19T22:16:40.600","lastModified":"2026-10-26T10:10:00.140"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-59992","identifiers":["CVE-2026-59992"]},"retained":false,"historicalAffected":[]},{"revision":"41dd0a192ee549249c42314e3c4a52da","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-8mq9-5fw2-5rm4","aliases":["CVE-2026-59992"],"title":"Tina: Broken Access Control: arbitrary bucket-key write/delete in `next-tinacms-s3` (and sibling production media adapters)","description":"## Summary\n\nThe production media handler shipped by `next-tinacms-s3` (`createMediaHandler` in `packages/next-tinacms-s3/src/handlers.ts`) accepts an attacker-chosen `?key=` query parameter and returns an AWS-signed `PutObject` URL whose `Key` is that value, with no check that the key falls under the operator's configured `mediaRoot`. The same handler's `DELETE` branch reads `objectKey = (req.query.media as string[])[1]` and dispatches a `DeleteObjectCommand` for that exact key, again unbounded by `mediaRoot`. Any caller that passes the operator-supplied `authorized()` predicate — i.e. any logged-in CMS editor in a typical TinaCloud / self-hosted deployment — therefore has write and delete authority over the *entire* S3 bucket the IAM key can reach, even though the package documents `mediaRoot` as the place where editors are scoped. The same shape is present in `next-tinacms-dos`, `next-tinacms-azure`, and `next-tinacms-cloudinary`, so a single design mistake spans every first-party production media backend.\n\n* Project: TinaCMS — first-party production media adapters (consumed by self-hosted Next.js sites and TinaCloud-backed deployments).\n* Source reviewed: `tinacms/tinacms` @ `main` (`b56dad4`).\n* Deployed artefact validated: `next-tinacms-s3@21.0.3` handler logic, exercised against `@aws-sdk/client-s3@3.665.x` via `aws-sdk-client-mock@4.1.0` (the AWS SDK signs the URL identically whether the bucket is real or mocked).\n* Affected file(s):\n  * `packages/next-tinacms-s3/src/handlers.ts:67-90` — `GET ?key=` returns presigned `PutObjectCommand` URL with attacker-chosen `Key`.\n  * `packages/next-tinacms-s3/src/handlers.ts:199-223` — `DELETE` reads `[, objectKey] = media` and issues `DeleteObjectCommand` against attacker-chosen `Key`.\n  * `packages/next-tinacms-dos/src/handlers.ts:79-152` and `:249-278` — same write/delete pattern, plus a server-side upload that builds the key with `path.join(mediaRoot, prefix + filename)` over attacker-controlled `directory` and `filename`.\n  * `packages/next-tinacms-azure/src/handlers.ts:44-95` — `uploadMedia` writes `path.join(directory, filename)` with both fields attacker-controlled (no `mediaRoot` configured at all); `deleteAsset` deletes any blob in the container.\n  * `packages/next-tinacms-cloudinary/src/handlers.ts:193-204` — `cloudinary.uploader.destroy(public_id)` over attacker-chosen `public_id`.\n* CWE: CWE-639 — Authorization Bypass Through User-Controlled Key. Adjacent: CWE-284 (Improper Access Control), CWE-862 (Missing Authorization on the per-key authority check).\n* OWASP 2021: **A01:2021 — Broken Access Control** (the operator's intended `mediaRoot` boundary is enforced only on listing, not on writes or deletes). Secondary: A04:2021 — Insecure Design (every adapter independently re-implements the same broken pattern).\n\n## Vulnerable code\n\n`packages/next-tinacms-s3/src/handlers.ts:39-98`:\n\n```ts\nexport const createMediaHandler = (config: S3Config, options?: S3Options) => {\n  const client = new S3Client(config.config);\n  const bucket = config.bucket;\n  let mediaRoot = config.mediaRoot || '';                 // (1)\n  if (mediaRoot) { /* normalise to \"media/\" form */ }\n\n  return async (req: NextApiRequest, res: NextApiResponse) => {\n    const isAuthorized = await config.authorized(req, res);\n    if (!isAuthorized) {\n      res.status(401).json({ message: 'sorry this user is unauthorized' });\n      return;\n    }\n    switch (req.method) {\n      case 'GET':\n        if (req.query.key) {\n          const expiresIn: number =\n            (req.query.expiresIn && Number(req.query.expiresIn)) || 3600; // (2)\n          const s3_key = req.query.key\n            ? Array.isArray(req.query.key) ? req.query.key[0] : req.query.key\n            : null;\n          if (!s3_key) return res.status(400).json({ message: 'key is required' });\n          if (await keyExists(client, bucket, s3_key)) {\n            return res.status(400).json({ message: 'key already exists' }); // (3)\n          }\n          const signedUrl = await getUploadUrl(bucket, s3_key, expiresIn, client); // (4)\n          return res.json({ signedUrl, src: cdnUrl + s3_key });\n        }\n        return listMedia(req, res, client, bucket, mediaRoot, cdnUrl);     // (5)\n      case 'DELETE':\n        return deleteAsset(req, res, client, bucket);                      // (6)\n```\n\n`packages/next-tinacms-s3/src/handlers.ts:199-223`:\n\n```ts\nasync function deleteAsset(req, res, client, bucket) {\n  const { media } = req.query;\n  const [, objectKey] = media as string[];                                  // (7)\n  const params: DeleteObjectCommandInput = { Bucket: bucket, Key: objectKey };\n  const command = new DeleteObjectCommand(params);\n  ...\n}\n```\n\nAt **(1)** the operator configures `mediaRoot` (e.g. `\"media/\"`), and the package's `README` documents this as the directory the IAM key is scoped to. At **(4)** the handler signs a `PutObjectCommand` with `Key: s3_key` taken verbatim from `req.query.key`. Nothing between (1) and (4) verifies that `s3_key` starts with `mediaRoot`, so any path the IAM key can reach is fair game. The only filter is the `keyExists` check at **(3)**, which prevents *overwrite* of an existing object but not creation of arbitrary new ones (and not overwrite of objects the IAM key cannot `HeadObject`). At **(2)** the validity window of the produced URL is also attacker-controlled, capped only by AWS SigV4's 7-day hard limit. At **(5)** the *list* path **does** prefix-join `mediaRoot` (`Prefix: mediaRoot ? path.join(mediaRoot, prefix) : prefix`), demonstrating that the boundary was understood to exist — it just isn't enforced on writes. At **(6)** + **(7)** delete extracts the second URL segment into `objectKey` with no prefix check, so `DELETE /api/s3/media/x/anything-in-the-bucket` is a valid arbitrary-key delete primitive.\n\nFor contrast, the same package's `listMedia` (line 139) does write `Prefix: mediaRoot ? path.join(mediaRoot, prefix) : prefix`, and `stripMediaRoot` (line 100) exists explicitly to peel `mediaRoot` off keys returned to the client — so the codebase models `mediaRoot` as a security boundary on the read side. The write and delete sides simply forgot to apply it.\n\nThe same misalignment is duplicated in three sibling packages:\n\n* `packages/next-tinacms-dos/src/handlers.ts:115-125` — upload builds `Key: mediaRoot ? path.join(mediaRoot, prefix + filename) : prefix + filename` over attacker-controlled `prefix` (from `req.body.directory`) and `filename` (from `multer`'s `file.originalname`); `path.join` collapses `..` segments, so a `directory` of `../..` plus a chosen `filename` lands at any key in the bucket. Lines 249-278 reproduce the S3 delete-by-second-segment pattern.\n* `packages/next-tinacms-azure/src/handlers.ts:44-95` — `uploadMedia` writes a blob at `path.join(directory, filename)` with both values straight out of `formData`. `next-tinacms-azure` has no `mediaRoot` config at all (`AzureBlobStorageConfig` in `src/types.ts`), so the entire container is writable / deletable by any authorized user.\n* `packages/next-tinacms-cloudinary/src/handlers.ts:193-204` — `deleteAsset` calls `cloudinary.uploader.destroy(public_id)` over the attacker-chosen second segment, deleting any asset in the cloud. The same handler's `listMedia` (line 110) interpolates `mediaListOptions.directory` directly into a Cloudinary search-expression DSL string (`folder=\"${directory}\"`), giving an authorized user full search-expression injection — out of scope for this finding but worth a separate report.\n\n## Reproduction (validated locally)\n\nEnvironment: Node 22, `@aws-sdk/client-s3@3.665.0`, `@aws-sdk/s3-request-presigner@3.665.0`, `aws-sdk-client-mock@4.1.0`. The harness vendors `createMediaHandler` byte-for-byte from `packages/next-tinacms-s3/src/handlers.ts` and runs it against a mocked `S3Client`. The AWS SDK signs the URL identically whether the bucket exists — the resulting URL is the same one a real S3 deployment would hand back to the editor's browser, so it would PUT against a real bucket without further help.\n\nPoC layout under `/Users/admin/joplin_research/tinacms-s3-poc/`:\n\n```\npackage.json            (deps only)\nhandler-vendored.mjs    (verbatim copy of createMediaHandler, types stripped)\npoc.mjs                 (four exhibits exercising the handler)\n```\n\nHandler is configured the way the package's `README` documents:\n\n```js\nconst handler = createMediaHandler({\n  config: { region: 'us-east-1', credentials: { accessKeyId: '…', secretAccessKey: '…' } },\n  bucket: 'editor-media-bucket',\n  mediaRoot: 'media',                 // editors are \"scoped to media/\"\n  authorized: async () => true,       // typical wiring: TinaCloud verified user\n});\n```\n\nRun:\n\n```bash\n$ cd /Users/admin/tinacms-s3-poc && node poc.mjs\n--- Exhibit A: presigned PUT URL for arbitrary bucket key ---\n  HTTP status : 200\n  signedUrl   : https://editor-media-bucket.s3.us-east-1.amazonaws.com/index.html?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAFA...\n  src         : https://editor-media-bucket.s3.us-east-1.amazonaws.com/index.html\n  signed Key  : /index.html\n  RESULT      : BYPASS  (signed URL writes outside media/)\n\n--- Exhibit B: cross-tenant overwrite via attacker-chosen key ---\n  signed Key  : /tenant-victim/posts/announcement.mdx\n  RESULT      : BYPASS  (cross-tenant write primitive granted)\n\n--- Exhibit C: arbitrary-key DELETE via [...media] route ---\n  HTTP status : 200\n  S3 Bucket   : editor-media-bucket\n  S3 Key      : tenant-victim/posts/announcement.mdx\n  RESULT      : BYPASS  (arbitrary-key DELETE granted)\n\n--- Exhibit D: attacker-controlled ?expiresIn ---\n  X-Amz-Expires: 604800 sec\n  RESULT       : BYPASS  (URL valid far longer than the 1h default)\n```\n\nVerification, in detail:\n\n1. **Exhibit A** — `GET /api/s3/media?key=index.html`. `mediaRoot=media/` is configured but never consulted on the write path. The handler hands back a SigV4 URL whose path is `/index.html`. PUT'ing any body to that URL would create the bucket-root object `index.html` against the operator's IAM credentials. For a deployment that fronts the bucket with CloudFront / S3-website hosting, this is a one-shot site-defacement primitive (and, for any CSP that trusts the bucket, a one-shot stored-XSS primitive against the published site).\n\n2. **Exhibit B** — same primitive, attacker-chosen key `tenant-victim/posts/announcement.mdx`. In the multi-tenant TinaCloud / TinaCMS Cloud deployment shape (one bucket shared across tenants, each tenant assigned a `mediaRoot/<tenant>/` prefix), a Tenant A editor receives a presigned PUT URL for any Tenant B key. Cross-tenant content tampering with no further bug needed.\n\n3. **Exhibit C** — `DELETE /api/s3/media/anything-here-is-ignored/tenant-victim%2Fposts%2Fannouncement.mdx`. The handler reads the catch-all as `req.query.media = ['anything-here-is-ignored', 'tenant-victim/posts/announcement.mdx']`, destructures to `objectKey = media[1]`, dispatches `DeleteObjectCommand({ Bucket: 'editor-media-bucket', Key: 'tenant-victim/posts/announcement.mdx' })`. The mock confirms the captured command's `Key` is the attacker-supplied string. Real S3 will honour the same call. (The mock-captured call confirms the *handler's* behaviour; the real S3 call requires only `s3:DeleteObject` on the bucket, which the package's `README` instructs operators to grant.)\n\n4. **Exhibit D** — `?expiresIn=604800` (7 days, the SigV4 ceiling). The handler accepts it, the resulting `X-Amz-Expires=604800`. Combined with Exhibit A, an attacker who briefly compromises a CMS session (e.g. via a stolen idle browser session or the dev-server CSRF write described in finding 06) can mint a long-lived offline write primitive — usable for a week with no further interaction with the CMS.\n\nThe four exhibits together: cross-mediaRoot write, cross-tenant write, arbitrary-key delete, and long-lived signed-URL minting.\n\n## Impact\n\n* **Cross-tenant / cross-mediaRoot write.** In any deployment where the bucket holds anything other than this one editor's `mediaRoot` (multi-tenant TinaCloud, shared corporate bucket, bucket-backed static site), an authorized editor obtains write authority over every key the IAM credential can reach. Concretely: defacement of a static site fronted by the bucket; cross-tenant content tampering; replacement of `*.html`, `*.js`, `*.json` assets that downstream consumers trust. (Exhibits A, B; `handlers.ts:67-90`.)\n* **Stored XSS via uploaded HTML.** S3 returns objects with the `Content-Type` the upload supplies. The presigned `PutObjectCommand` URL carries no `Content-Type` constraint — the attacker's `PUT` chooses it. An editor can therefore write an HTML / SVG / JS payload to the bucket-root, and any CDN or origin that serves bucket objects directly to an authenticated SaaS surface inherits stored XSS in that origin.\n* **Arbitrary-key delete (data destruction).** Through `[...media]` the handler treats the second URL segment as a free-form object key. An editor can wipe any object the IAM key can `s3:DeleteObject`, including non-media artefacts like deployment manifests, backups, or other tenants' files. (Exhibit C; `handlers.ts:199-223`.)\n* **Bucket-policy implications.** The `README` (lines 38-90) tells operators to grant the IAM key `s3:PutObject`, `s3:PutObjectAcl`, `s3:DeleteObject`, and `s3:ListBucket` on the *whole* bucket. The README implies the application code constrains the editor's actions to `mediaRoot`. Because the application code does not, every operator who follows the README has — without realising — granted every CMS editor full write/delete on the whole bucket.\n* **Long-lived offline write primitive.** `expiresIn` is attacker-controlled and not capped by the handler. A single GET request mints a URL valid for up to 7 days (AWS SigV4 ceiling). An attacker who briefly compromises a CMS session can stash one such URL and continue writing arbitrary objects long after the session is revoked. (Exhibit D.)\n* **Same-shape exposure on every other first-party media adapter.** `next-tinacms-dos` (DigitalOcean Spaces, S3-compatible), `next-tinacms-azure` (Blob Storage), and `next-tinacms-cloudinary` (Cloudinary) all duplicate the broken pattern. Azure has *no* mediaRoot config at all, so the boundary that `next-tinacms-s3` advertises but doesn't enforce isn't even nominally present in `next-tinacms-azure`.","state":0,"publishedAt":"2026-08-19T21:56:30+00:00","modifiedAt":"2026-08-21T18:45:12.8989702+00:00","affected":[{"ecosystem":"npm","name":"next-tinacms-s3","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"23.0.4"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"npm","name":"next-tinacms-dos","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"23.0.4"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"npm","name":"next-tinacms-azure","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"14.0.4"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"npm","name":"next-tinacms-cloudinary","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"26.0.4"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/tinacms/tinacms/security/advisories/GHSA-8mq9-5fw2-5rm4","https://nvd.nist.gov/vuln/detail/CVE-2026-59992","https://github.com/tinacms/tinacms/pull/7088","https://github.com/tinacms/tinacms/commit/d44558e9b4502d4f4fc2c970d22985339fe2b6ce","https://github.com/tinacms/tinacms","https://github.com/tinacms/tinacms/releases/tag/next-tinacms-s3@23.0.4"],"sourceUrl":"https://github.com/advisories/GHSA-8mq9-5fw2-5rm4","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-8mq9-5fw2-5rm4","identifiers":["GHSA-8mq9-5fw2-5rm4","CVE-2026-59992"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-07T20:07:23.8593986+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-07T20:07:23.8593986+00:00","lastSuccessAt":"2026-11-07T20:07:24.014368+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:24.014369+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-bb5c9d5a08063ef076042e33dd72a7d0ffa4c4487efdcf3106e29ba55ec2ad57-aac6631b84e101279eb6b1c3835f52bf7b26c50f4d25bc0fb35977235593f4a7","complete":true,"records":420943,"lastAttemptAt":"2026-11-07T20:07:23.1739993+00:00","lastSuccessAt":"2026-11-07T20:07:23.0712879+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:23.1739993+00:00","snapshotPublishedAt":"2026-11-07T19:18:33+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-c5a2504558b549559b8658a653596391","complete":true,"records":13633,"lastAttemptAt":"2026-11-07T20:19:26.3099435+00:00","lastSuccessAt":"2026-11-07T20:19:26.304+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:19:26.304+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-07T20:07:23.8593986+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-07T20:07:23.8593986+00:00","lastSuccessAt":"2026-11-07T20:07:24.0152025+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:24.0152028+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-191690d52ba04a91b1a2749fa6191ceb","complete":true,"records":13645,"lastAttemptAt":"2026-11-07T20:07:42.8841875+00:00","lastSuccessAt":"2026-11-07T20:07:42.881+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:42.881+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-0eb4d6dde62dffacd040ad992d4fd5a65cd7d0dc5f5f4cfa8b41967fc6f9ca5f","complete":true,"records":9914,"lastAttemptAt":"2026-11-07T20:07:23.800032+00:00","lastSuccessAt":"2026-11-06T20:10:36.6450506+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:23.800032+00:00","snapshotPublishedAt":"2026-11-06T20:02:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-07T20:07:23.2420378+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:23.2420378+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-335633e78a15c9cde10a6025895b5617717087fb945489ae7e15d377bacf34a9","complete":true,"records":3055,"lastAttemptAt":"2026-11-07T20:07:23.8227715+00:00","lastSuccessAt":"2026-11-06T16:03:00.7615789+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:23.8227715+00:00","snapshotPublishedAt":"2026-11-06T16:02:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-40ad6183a951565ace1520a9b4d631e1d0bfb1d4bf7c5574c375032559f3ff53","complete":true,"records":231292,"lastAttemptAt":"2026-11-07T20:07:23.4659623+00:00","lastSuccessAt":"2026-11-07T10:29:00.4821259+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:07:23.4659623+00:00","snapshotPublishedAt":"2026-11-07T09:32:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}