CVE-2026-113655
ApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, an authenticated editor with legitimate edit rights on a pre-existing ordinary page nested under a restricted parent can use the same-parent page move operation to change sibling ranks without create authorization on that parent. The non-default nesting must already exist, and the bypass is limited to sibling ordering rather than arbitrary content modification, confidentiality loss, or creation of the restricted nesting. This issue is fixed in version 4.33.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
4.3
ApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, an authenticated editor with legitimate edit rights on a pre-existing ordinary page nested under a restricted parent can use the same-parent page move operation to change sibling ranks without create authorization on that parent. The non-default nesting must already exist, and the bypass is limited to sibling ordering rather than arbitrary content modification, confidentiality loss, or creation of the restricted nesting. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113655- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
EUVDEUVD-2026-105183
4.3
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, an authenticated editor with legitimate edit rights on a pre-existing ordinary page nested under a restricted parent can use the same-parent page move operation to change sibling ranks without create authorization on that parent. The non-default nesting must already exist, and the bypass is limited to sibling ordering rather than arbitrary content modification, confidentiality loss, or creation of the restricted nesting. This issue is fixed in version 4.33.0.
- Record
EUVD-2026-105183- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2jrp-qc93-h2j8
4.3
ApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
Summary
ApostropheCMS enforces per-type authorization on pages: a page type may declare editRole / publishRole (the core @apostrophecms/archive-page does), so a project can define restricted subtrees that only higher-privileged roles may create or edit within. The move() operation in the @apostrophecms/page module is supposed to enforce that a page may only be moved into a parent the actor has create rights over, the same boundary the page-insert route enforces. The destination-parent authorization check in move() is gated on the move being a cross-parent move (oldParent._id !== parent._id). A same-parent reorder (repositioning a page among its existing siblings under the same parent) short-circuits that check entirely, so an authenticated low-privileged editor who holds edit rights on one ordinary page nested under a restricted parent can reposition it and, in doing so, trigger an unguarded bulk database write that re-ranks the restricted parent's other children, documents the editor has no authorization to modify.
Root Cause
The security invariant is that mutating the sibling ordering of a restricted parent's children requires create authority on that parent, which is exactly what the page-insert path enforces (insert() throws forbidden when !parent._create before it nudges peers). The destination authorization guard in move() restores this boundary only for cross-parent moves: its leading conjunct is (oldParent._id !== parent._id). For a same-parent reorder both oldParent and parent are the identical restricted parent, so the conjunct is false, the whole conditional short-circuits, and the parent._create requirement is never evaluated. The only surviving gate on the path is moved._edit, which the editor legitimately holds for the ordinary page being moved. The subsequent sibling re-rank is issued as a raw MongoDB updateMany that never passes through the per-document beforeUpdate edit check, so the restricted siblings are modified with no authorization at all.
Affected function: @apostrophecms/page move() (inner body)
packages/apostrophe/modules/@apostrophecms/page/index.js:1684-1691
if (
(oldParent._id !== parent._id) && // false for a same-parent reorder -> whole check skipped
(parent.type !== '@apostrophecms/archive-page') &&
(!parent._create) &&
!(oldParent.type === '@apostrophecms/archive-page' && parent._edit)
) {
throw self.apos.error('forbidden');
}
Unguarded sink: nudgeNewPeers()
packages/apostrophe/modules/@apostrophecms/page/index.js:1781-1803
await self.apos.doc.db.updateMany(
{ path: matchDescendants(parent), level: parent.level + 1,
aposLocale: { $in: [ draft, published ] }, rank: { $gte: rank } },
{ $inc: { rank: 1 } }); // no permission clause; bypasses beforeUpdate
Contrasting correct boundary: insert()
packages/apostrophe/modules/@apostrophecms/page/index.js:1436-1439
if (!parent._create) { throw self.apos.error('forbidden'); } // enforced for every position
Impact
The attacker is an authenticated low-privileged user, an editor (or a contributor operating in draft mode), who holds edit rights on at least one ordinary page that is nested as a direct child of a restricted-type parent. No admin rights and no special tokens are required. Authorization in this CMS is resolved per page type, not per subtree: a page's editability is judged by its own type's editRole, so an ordinary public-page child can be editable by an editor even though its restricted secret-page-type parent (declaring editRole: admin) is not. Exploiting the gap, the editor can persistently alter the rank (sibling ordering, which drives navigation and menu order) of the restricted parent's other children, documents on which the editor holds no edit, create, publish, or delete authority. Repeating the reposition monotonically inflates those siblings' ranks. This is an…
- Record
GHSA-2jrp-qc93-h2j8- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
NVD
4.3
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, an authenticated editor with legitimate edit rights on a pre-existing ordinary page nested under a restricted parent can use the same-parent page move operation to change sibling ranks without create authorization on that parent. The non-default nesting must already exist, and the bypass is limited to sibling ordering rather than arbitrary content modification, confidentiality loss, or creation of the restricted nesting. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113655- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
OSV
4.3
ApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, an authenticated editor with legitimate edit rights on a pre-existing ordinary page nested under a restricted parent can use the same-parent page move operation to change sibling ranks without create authorization on that parent. The non-default nesting must already exist, and the bypass is limited to sibling ordering rather than arbitrary content modification, confidentiality loss, or creation of the restricted nesting. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113655- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
OSVGHSA-2jrp-qc93-h2j8
4.3
ApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
Summary
ApostropheCMS enforces per-type authorization on pages: a page type may declare editRole / publishRole (the core @apostrophecms/archive-page does), so a project can define restricted subtrees that only higher-privileged roles may create or edit within. The move() operation in the @apostrophecms/page module is supposed to enforce that a page may only be moved into a parent the actor has create rights over, the same boundary the page-insert route enforces. The destination-parent authorization check in move() is gated on the move being a cross-parent move (oldParent._id !== parent._id). A same-parent reorder (repositioning a page among its existing siblings under the same parent) short-circuits that check entirely, so an authenticated low-privileged editor who holds edit rights on one ordinary page nested under a restricted parent can reposition it and, in doing so, trigger an unguarded bulk database write that re-ranks the restricted parent's other children, documents the editor has no authorization to modify.
Root Cause
The security invariant is that mutating the sibling ordering of a restricted parent's children requires create authority on that parent, which is exactly what the page-insert path enforces (insert() throws forbidden when !parent._create before it nudges peers). The destination authorization guard in move() restores this boundary only for cross-parent moves: its leading conjunct is (oldParent._id !== parent._id). For a same-parent reorder both oldParent and parent are the identical restricted parent, so the conjunct is false, the whole conditional short-circuits, and the parent._create requirement is never evaluated. The only surviving gate on the path is moved._edit, which the editor legitimately holds for the ordinary page being moved. The subsequent sibling re-rank is issued as a raw MongoDB updateMany that never passes through the per-document beforeUpdate edit check, so the restricted siblings are modified with no authorization at all.
Affected function: @apostrophecms/page move() (inner body)
packages/apostrophe/modules/@apostrophecms/page/index.js:1684-1691
if (
(oldParent._id !== parent._id) && // false for a same-parent reorder -> whole check skipped
(parent.type !== '@apostrophecms/archive-page') &&
(!parent._create) &&
!(oldParent.type === '@apostrophecms/archive-page' && parent._edit)
) {
throw self.apos.error('forbidden');
}
Unguarded sink: nudgeNewPeers()
packages/apostrophe/modules/@apostrophecms/page/index.js:1781-1803
await self.apos.doc.db.updateMany(
{ path: matchDescendants(parent), level: parent.level + 1,
aposLocale: { $in: [ draft, published ] }, rank: { $gte: rank } },
{ $inc: { rank: 1 } }); // no permission clause; bypasses beforeUpdate
Contrasting correct boundary: insert()
packages/apostrophe/modules/@apostrophecms/page/index.js:1436-1439
if (!parent._create) { throw self.apos.error('forbidden'); } // enforced for every position
Impact
The attacker is an authenticated low-privileged user, an editor (or a contributor operating in draft mode), who holds edit rights on at least one ordinary page that is nested as a direct child of a restricted-type parent. No admin rights and no special tokens are required. Authorization in this CMS is resolved per page type, not per subtree: a page's editability is judged by its own type's editRole, so an ordinary public-page child can be editable by an editor even though its restricted secret-page-type parent (declaring editRole: admin) is not. Exploiting the gap, the editor can persistently alter the rank (sibling ordering, which drives navigation and menu order) of the restricted parent's other children, documents on which the editor holds no edit, create, publish, or delete authority. Repeating the reposition monotonically inflates those siblings' ranks. This is an…
- Record
GHSA-2jrp-qc93-h2j8- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639, CWE-862
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
apostrophenpm |
before 4.33.0 | 4.33.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-113655 is a medium vulnerability (CVSS 4.3) in Apostrophecms Apostrophe, published October 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.32%, the 25th percentile. Fixed in apostrophe 4.33.0.
References
github.com/apostrophecms/apostrophe/security/advisories/GHSA-2jrp-qc93-h2j8
github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
github.com/apostrophecms/apostrophe/releases/tag/[email protected]
github.com/advisories/GHSA-2jrp-qc93-h2j8
github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113655.json
nvd.nist.gov/vuln/detail/CVE-2026-113655
github.com/apostrophecms/apostrophe
Left out here: the rest of 2 source descriptions. The JSON record has them.