GHSA-2j32-q6rx-h844

Suggest an improvement
Source
https://github.com/advisories/GHSA-2j32-q6rx-h844
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-2j32-q6rx-h844/GHSA-2j32-q6rx-h844.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-2j32-q6rx-h844
Aliases
Published
2026-10-20T18:13:08Z
Modified
2026-10-20T18:41:05Z
Severity
  • 5.3 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N CVSS Calculator
Summary
ApostropheCMS: Page REST API authorization bypass
Details

Summary The core page REST API reads full page documents with .permission(false) in getOne and in the all=1 tree dump, bypassing page-type viewRole, visibility: private, and visibility: loginRequired checks. Unauthenticated visitors and low-privilege contributors can read admin-only and non-public page content by _id/slug.

Details packages/apostrophe/modules/@apostrophecms/page/index.js:

  • getOne (L456–465): await self.publicApiCheckAsync(req); ... self.getRestQuery(req).permission(false).and(criteria) — no viewRole threshold, no visibility filtering.
  • all handler (L318–395): getRestQuery(req).permission(false) (L384–385) walks every page type and returns the projection including visibility.
  • publicApiCheckAsync (L3044) only checks that publicApiProjection exists — it performs no authorization.
  • Correct control behaves differently: piece-type/index.js:getOne (L319–327) uses getRestQuery(req) without .permission(false) and enforces viewRole.

PoC

# Anonymous (no session) — private, admin-only page:
curl "http://localhost:3100/api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:published"
# → 200  {"_id":"...:en:published","title":"Secret Private Page","slug":"/secret-page",
#         "type":"private-type","visibility":"private","secretData":"TOP-SECRET-VALUE-abc123"}

# Anonymous full site-map dump (includes private + loginRequired pages):
curl "http://localhost:3100/api/v1/@apostrophecms/page?all=1"        # → 200, full tree

# Contributor (cont_admin) reads full admin-only doc incl. protected field:
curl -b contrib.cookies ".../api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:published"
# → 200  secretData = TOP-SECRET-VALUE-abc123
# Controls (correctly enforced):
curl -b contrib.cookies ".../api/v1/@apostrophecms/page?type=private-type&autocomplete=zz"   # → 403
curl -b contrib.cookies ".../api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:draft"   # → 403

Impact Unauthenticated disclosure of visibility: private and loginRequired pages (full documents, not just paths) and of the complete site structure with visibility/type metadata; contributors read admin-only page types and their protected fields. Because _ids are enumerable and leaked by all=1, this is systemic.

Remediation — Remove .permission(false) from getOne/all or re-apply the type's viewRole threshold plus the same visibility/login-required criteria the list and autocomplete paths use (mirror piece-type); make publicApiCheck config-presence-only and add an explicit authorization step per verb.

Database specific
{
    "cwe_ids":  [
        "CWE-863"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-10-20T18:13:08Z",
    "nvd_published_at":  null,
    "severity":  "MODERATE"
}
References

Affected packages

npm / apostrophe

Package

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
4.33.0

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-2j32-q6rx-h844/GHSA-2j32-q6rx-h844.json"