{"id":"CVE-2026-112957","kind":"vulnerability","aliases":["CVE-2026-112957","GHSA-vh56-5m88-39rq"],"related":[],"title":"ApostropheCMS: GET /api/v1/@apostrophecms/page/:id bypasses viewRole restriction and exposes viewPermission-gated fields to lower-privileged users","description":"### Summary\nThe `getOne` REST endpoint for pages does not apply `viewRole` type-level restrictions\nor `viewPermission` field-level redaction. An editor-level user who knows a document\n`_id` can read the full content of admin-only pages and their privileged fields, while\nthe `getAll` endpoint correctly enforces both restrictions.\n\n### Details\n**Finding 3 — viewRole bypass:**\n`modules/@apostrophecms/page/index.js` exposes `getRestQuery(req, omitPermissionCheck =\nfalse)`. The `getAll` path calls `publicApiCheckAsync` and threads the full query builder\npipeline including `viewRole` filtering. The `getOne` path uses a divergent code path\nthat skips this pipeline.\n\nA page type declared with `viewRole: 'admin'` is inaccessible via `GET\n/api/v1/@apostrophecms/page?type=secretPage` (returns 403 to an editor) but is fully\nreturned by `GET /api/v1/@apostrophecms/page/:id:en:published` to the same editor.\n\n**Finding 4 — viewPermission field leak:**\nFields decorated with `viewPermission: { action: 'edit', type: '@apostrophecms/page' }`\nare stripped by `removeForbiddenFields` in the `getAll` path but are returned verbatim\nin `getOne`. An editor who knows any document `_id` can read all\n`viewPermission`-restricted fields.\n\n### PoC\n```bash\n# Log in as editor, get session jar\nEDITOR_JAR=\"...\"\n\n# getAll — correctly blocked\ncurl -b \"$EDITOR_JAR\" \\\n  \"http://TARGET/api/v1/@apostrophecms/page?type=secretPage\"\n# → 403 Forbidden\n\n# getOne — bypassed, returns full document including viewPermission fields\ncurl -b \"$EDITOR_JAR\" \\\n  \"http://TARGET/api/v1/@apostrophecms/page/ADMIN_PAGE_ID:en:published\"\n# → 200 OK, full doc including secretField value\n```\n\n### Impact\nEditors can read the full content of any page — including those restricted to admins via\n`viewRole` — if they know or can guess the `_id`. Sensitive field values gated by\n`viewPermission` are also fully exposed. This breaks the content access control model\nfor any site using these decorators.","summary":"ApostropheCMS's single-page REST endpoint GET /api/v1/@apostrophecms/page/:id skips the viewRole and viewPermission checks that the list endpoint enforces. An editor-level user who knows or guesses a page _id can read admin-only pages and fields gated by viewPermission.","severity":"medium","cvss":4.300000190734863,"epss":0.0025100000202655792,"exploited":false,"exploited_at":null,"products":[{"name":"apostrophe","fixed":["4.34.0"],"affected":"< 4.34.0","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["apostrophe","cms","cwe-862","node","package","self-hosted"],"not_affected_if":["editor-role-untrusted-users-absent","no-viewrole-or-viewpermission-usage","version-4.34.0-or-later"],"how_to_check":"Check the installed apostrophe version in package.json or package-lock.json (npm ls apostrophe); affected if below 4.34.0.\nSearch the project for viewRole or viewPermission to see whether any page types or fields rely on them.","fix":"Upgrade apostrophe to 4.34.0 or later.","status":"open","published_at":"2026-10-20T17:17:33+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T20:03:19+00:00","updated_at":"2026-10-23T21:57:20+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-23T20:03:19Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade apostrophe to 4.34.0 or later.\nUnknown whether a workaround exists before upgrading.","summary":"ApostropheCMS's single-page REST endpoint GET /api/v1/@apostrophecms/page/:id skips the viewRole and viewPermission checks that the list endpoint enforces. An editor-level user who knows or guesses a page _id can read admin-only pages and fields gated by viewPermission.","category":"package","who_cares":["apostrophe","node","cms","self-hosted"],"confidence":0.8,"analyst_note":"","how_to_check":"Check the installed apostrophe version in package.json or package-lock.json (npm ls apostrophe); affected if below 4.34.0.\nSearch the project for viewRole or viewPermission to see whether any page types or fields rely on them.","needs_analyst":false,"not_affected_if":["version-4.34.0-or-later","no-viewRole-or-viewPermission-usage","editor-role-untrusted-users-absent"],"severity_if_unknown":"medium"},"cost_usd":0.000462,"input_tokens":2789,"output_tokens":366,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-112957':22A,30A '-2026':21A,29A '-862':35B '/api/v1':3A,49B,189C,204C '200':293C '3':141C '39rq':27A '4':214C '403':194C,275C '5m88':26A 'access':339C 'action':222C 'admin':78B,124C,184C,314C 'admin-on':77B,123C 'also':332C 'apostroph':32B 'apostrophe':31A 'apostrophecm':1A,41B 'apostrophecms/page':4A,50B,190C,205C,225C,272C 'apostrophecms/page/admin_page_id':290C 'apostrophecms/page/index.js':145C 'appli':95C 'b':268C,286C 'bash':254C 'block':266C 'break':336C 'builder':161C 'bypass':6A,143C,278C 'call':154C 'check':57B 'cms':33B 'code':172C 'content':121C,306C,338C 'control':340C 'correct':135C,265C 'curl':267C,285C 'cve':20A,28A 'cwe':34B 'declar':181C 'decor':219C,347C 'detail':139C 'diverg':171C 'doc':296C 'document':115C,245C,281C 'edit':223C 'editor':65B,109C,197C,212C,241C,258C,262C,269C,287C,301C 'editor-level':64B,108C 'en':207C,291C 'endpoint':47B,61B,90C,134C 'enforc':62B,136C 'expos':10A,146C,334C 'fals':150C 'field':14A,82B,104C,130C,216C,218C,252C,284C,326C 'field-level':103C 'filter':165C 'find':140C,213C 'forbidden':276C 'full':120C,159C,280C,295C,305C 'fulli':200C,333C 'gate':13A,83B,328C 'get':2A,48B,188C,203C,259C 'getal':133C,152C,232C,264C 'geton':88C,167C,239C,277C 'getrestqueri':147C 'ghsa':24A 'ghsa-vh56-5m88-39rq':23A 'guess':71B,322C 'host':40B 'id':5A,51B,74B,116C,206C,246C,324C 'impact':300C 'inaccess':186C 'includ':163C,282C,297C,310C 'jar':261C,263C,270C,288C 'know':69B,113C,243C,319C 'leak':217C 'level':66B,99C,105C,110C 'list':60B 'log':255C 'lower':17A 'lower-privileg':16A 'model':341C 'modul':144C 'node':36B 'ok':294C 'omitpermissioncheck':149C 'packag':37B 'page':45B,73B,80B,92C,126C,179C,309C 'path':153C,168C,173C,233C 'pipelin':162C,177C 'poc':253C 'privileg':18A,129C 'publicapicheckasync':155C 'publish':208C,292C 'queri':160C 'read':76B,118C,248C,303C 'redact':106C 'removeforbiddenfield':229C 'req':148C 'rest':46B,89C 'restrict':8A,100C,138C,251C,312C 'return':193C,201C,236C,279C 'secretfield':298C 'secretpag':192C,274C 'self':39B 'self-host':38B 'sensit':325C 'session':260C 'singl':44B 'single-pag':43B 'site':344C 'skip':52B,175C 'strip':227C 'summari':86C 'target/api/v1':271C,289C 'thread':157C 'type':98C,180C,191C,224C,273C 'type-level':97C 'use':169C,345C 'user':19A,67B,111C 'valu':299C,327C 'verbatim':237C 'vh56':25A 'via':187C,315C 'viewpermiss':12A,56B,85B,102C,215C,221C,250C,283C,330C 'viewpermission-g':11A 'viewrol':7A,54B,96C,142C,164C,183C,316C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-20T17:17:33+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-vh56-5m88-39rq","url":"https://github.com/advisories/GHSA-vh56-5m88-39rq","severity":"medium","published_at":"2026-10-21T17:15:21+00:00","modified_at":"2026-10-21T17:15:22+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-112957","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112957","severity":null,"published_at":"2026-10-20T17:17:33+00:00","modified_at":"2026-10-21T16:17:15+00:00","first_seen_at":"2026-10-23T21:57:20+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"}