apostrophe
Ecosystem: npm
Downloads: 84.41 K
- 2CriticalAffects 2 versions
- 8HighAffects 8 versions
- 0MediumAffects 0 versions
- 20ModerateAffects 20 versions
- 4LowAffects 4 versions
- Critical2 (5.9%)
- High8 (23.5%)
- Medium0 (0.0%)
- Moderate20 (58.8%)
- Low4 (11.8%)
Monthly breakdown
- Critical
- High
- Medium
- Moderate
- Low
- 18 vulnerabilities
- Critical: 0
- High: 3
- Medium: 0
- Moderate: 14
- Low: 1
- 2 vulnerabilities
- Critical: 0
- High: 1
- Medium: 0
- Moderate: 1
- Low: 0
- 2 vulnerabilities
- Critical: 1
- High: 0
- Medium: 0
- Moderate: 0
- Low: 1
- 2 vulnerabilities
- Critical: 0
- High: 2
- Medium: 0
- Moderate: 0
- Low: 0
- 5 vulnerabilities
- Critical: 0
- High: 1
- Medium: 0
- Moderate: 3
- Low: 1
- 1 vulnerabilities
- Critical: 0
- High: 1
- Medium: 0
- Moderate: 0
- Low: 0
- 2 vulnerabilities
- Critical: 1
- High: 0
- Medium: 0
- Moderate: 1
- Low: 0
- 2 vulnerabilities
- Critical: 0
- High: 0
- Medium: 0
- Moderate: 1
- Low: 1
Vulnerabilities (34)
Known vulnerabilities affecting apostrophe, based on data from OSV.
| ID | Severity | Summary | Affected versions | Published | Source |
|---|---|---|---|---|---|
| GHSA-5m22-7pfq-vp65 | Moderate | ApostropheCMS: Import API bypasses publication roles for protected content## Review scope and evidence limits Package: @apostrophecms/import-export, version 3.7.0. Release tag reviewed: @apostrophecms/import-export@3.7.0, commit 9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce (September 30, 2026). Current main reviewed: 5ce3732be0ebdebb4db0f8ef160c81e8671157e6. The importer, gzip extractor, import permission gate, and core permission implementation have no differences between that release tag and the reviewed main. This follow-up is a static remediation review. We have not repeated the original end-to-end reproductions against the latest release or tested a deployed si |
| GitHub (opens in a new tab) | |
| GHSA-7jg5-frgc-c67w | Moderate | ApostropheCMS: Stored XSS via Unescaped Widget Style Attribute Serialization## Summary ApostropheCMS widget styles support project-defined schema fields. If a project defines a free-form `string` style field with a CSS `property` and no selector, an editor-controlled value is rendered as an inline CSS declaration. The resulting declaration is then inserted into the widget wrapper's `style="..."` attribute without HTML-attribute escaping. An attacker who can edit the widget can include a double quote in the style value, terminate the `style` attribute, and inject new HTML event-handler attributes. The payload is stored with the widget and executes in the Apostrophe s |
| GitHub (opens in a new tab) | |
| GHSA-86wm-68pq-5jwq | High | ApostropheCMS: Unauthenticated resource exhaustion via pre-auth aposBigUpload processing in @apostrophecms/import-export## Summary A potential unauthenticated denial-of-service vulnerability exists in the chunked import upload path used by `@apostrophecms/import-export`. The affected import API routes invoke: `self.apos.http.bigUploadMiddleware()` before the actual import handler performs its authentication check. Because no `authorize` callback is supplied to `bigUploadMiddleware()`, unauthenticated requests can reach the `aposBigUpload` `start`, `chunk`, and `end` operations before the later `req.user` check rejects the import. As a result, an unauthenticated remote attacker can create server-side uploa |
| GitHub (opens in a new tab) | |
| GHSA-f66c-mpj2-3r8h | Moderate | ApostropheCMS: POST /api/v1/@apostrophecms/doc/slugTaken discloses existence of private/admin-only content to any authenticated user### Summary The `slugTaken` endpoint returns a boolean indicating whether a slug is in use across all document types and visibility levels, with no permission or type filter. Any logged-in user (including editors) can enumerate the slug space of private pages, drafts, and archived content. ### Details `POST /api/v1/@apostrophecms/doc/slugTaken` with body `{ "slug": "target-slug" }` runs a raw existence check against the documents collection. It does not: - Check whether the requesting user has read permission for documents of that type. - Filter by `visibility` (public, loginRequired, private |
| GitHub (opens in a new tab) | |
| GHSA-rqgm-cfrp-4hx8 | Moderate | ApostropheCMS: Unauthenticated publicApiProjection bypass via search and sort query builders in the public REST API### Summary ApostropheCMS lets a module expose an anonymous read API by setting `publicApiProjection`, which whitelists the document fields unauthenticated callers may see. The public REST GET handler applies every launderable query builder from the query string *before* forcing that projection. Two of those builders, `search` and `sort`, operate on the underlying documents rather than the projected output, and neither is checked against the projection. An unauthenticated attacker can therefore: - use `?search=<word>` as a **value oracle** to confirm and locate documents by the content of a f |
| GitHub (opens in a new tab) | |
| GHSA-vh56-5m88-39rq | Moderate | ApostropheCMS: GET /api/v1/@apostrophecms/page/:id bypasses viewRole restriction and exposes viewPermission-gated fields to lower-privileged users### Summary The `getOne` REST endpoint for pages does not apply `viewRole` type-level restrictions or `viewPermission` field-level redaction. An editor-level user who knows a document `_id` can read the full content of admin-only pages and their privileged fields, while the `getAll` endpoint correctly enforces both restrictions. ### Details **Finding 3 — viewRole bypass:** `modules/@apostrophecms/page/index.js` exposes `getRestQuery(req, omitPermissionCheck = false)`. The `getAll` path calls `publicApiCheckAsync` and threads the full query builder pipeline including `viewRole` filtering. The |
| GitHub (opens in a new tab) | |
| GHSA-vmgj-9cwm-cf3h | Moderate | ApostropheCMS: Color field validation bypass allows stored XSS via HTML appended after rgb()/hsl() prefix### Summary The color field accepts any string that *begins* with a valid `rgb()` / `hsl()` color, because `TinyColor.isValid` does not anchor its match at the end of the string. An authenticated user with edit rights can store arbitrary HTML after a valid color prefix. When the stored value is rendered , the payload executes, resulting in stored XSS. ### Details Server-side validation of the color field relies on TinyColor: ```js const { TinyColor } = require('@ctrl/tinycolor'); if (!new TinyColor(value).isValid) throw self.apos.error('invalid'); ``` TinyColor's `rgb`/`hsl` matchers are no |
| GitHub (opens in a new tab) | |
| GHSA-2j32-q6rx-h844 | Moderate | ApostropheCMS: Page REST API authorization bypass**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` h |
| GitHub (opens in a new tab) | |
| GHSA-2jrp-qc93-h2j8 | Moderate | 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 |
| GitHub (opens in a new tab) | |
| GHSA-653j-g8j7-gh54 | Moderate | ApostropheCMS: No Attempt Throttling on Authenticated Password-Change Endpoint Enables Unlimited Current-Password Guessing**Summary** The self-service password-change endpoint verifies the current password but does not use the login module’s failed-attempt counter, throttle, delay, or lockout mechanism. An attacker with a valid authenticated session can submit repeated guesses for the current password without receiving a lockout or throttle response. **Exact source path** packages/apostrophe/modules/@apostrophecms/settings/index.js:621-664 **The password-change route is:** PATCH /api/v1/@apostrophecms/settings/password **The route calls the password verifier directly through:** await self.handleProtectedSubfor |
| GitHub (opens in a new tab) |
- GHSA-5m22-7pfq-vp65ModerateApostropheCMS: Import API bypasses publication roles for protected content
< 4.34.0
## Review scope and evidence limits Package: @apostrophecms/import-export, version 3.7.0. Release tag reviewed: @apostrophecms/import-export@3.7.0, commit 9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce (September 30, 2026). Current main reviewed: 5ce3732be0ebdebb4db0f8ef160c81e8671157e6. The importer, gzip extractor, import permission gate, and core permission implementation have no differences between that release tag and the reviewed main. This follow-up is a static remediation review. We have not repeated the original end-to-end reproductions against the latest release or tested a deployed si
CVE: CVE-2026-113649
- GHSA-7jg5-frgc-c67wModerateApostropheCMS: Stored XSS via Unescaped Widget Style Attribute Serialization
< 4.34.0
## Summary ApostropheCMS widget styles support project-defined schema fields. If a project defines a free-form `string` style field with a CSS `property` and no selector, an editor-controlled value is rendered as an inline CSS declaration. The resulting declaration is then inserted into the widget wrapper's `style="..."` attribute without HTML-attribute escaping. An attacker who can edit the widget can include a double quote in the style value, terminate the `style` attribute, and inject new HTML event-handler attributes. The payload is stored with the widget and executes in the Apostrophe s
CVE: CVE-2026-112958
- GHSA-86wm-68pq-5jwqHighApostropheCMS: Unauthenticated resource exhaustion via pre-auth aposBigUpload processing in @apostrophecms/import-export
< 4.33.0
## Summary A potential unauthenticated denial-of-service vulnerability exists in the chunked import upload path used by `@apostrophecms/import-export`. The affected import API routes invoke: `self.apos.http.bigUploadMiddleware()` before the actual import handler performs its authentication check. Because no `authorize` callback is supplied to `bigUploadMiddleware()`, unauthenticated requests can reach the `aposBigUpload` `start`, `chunk`, and `end` operations before the later `req.user` check rejects the import. As a result, an unauthenticated remote attacker can create server-side uploa
CVE: CVE-2026-113711
- GHSA-f66c-mpj2-3r8hModerateApostropheCMS: POST /api/v1/@apostrophecms/doc/slugTaken discloses existence of private/admin-only content to any authenticated user
< 4.34.0
### Summary The `slugTaken` endpoint returns a boolean indicating whether a slug is in use across all document types and visibility levels, with no permission or type filter. Any logged-in user (including editors) can enumerate the slug space of private pages, drafts, and archived content. ### Details `POST /api/v1/@apostrophecms/doc/slugTaken` with body `{ "slug": "target-slug" }` runs a raw existence check against the documents collection. It does not: - Check whether the requesting user has read permission for documents of that type. - Filter by `visibility` (public, loginRequired, private
CVE: CVE-2026-112952
- GHSA-rqgm-cfrp-4hx8ModerateApostropheCMS: Unauthenticated publicApiProjection bypass via search and sort query builders in the public REST API
< 4.34.0
### Summary ApostropheCMS lets a module expose an anonymous read API by setting `publicApiProjection`, which whitelists the document fields unauthenticated callers may see. The public REST GET handler applies every launderable query builder from the query string *before* forcing that projection. Two of those builders, `search` and `sort`, operate on the underlying documents rather than the projected output, and neither is checked against the projection. An unauthenticated attacker can therefore: - use `?search=<word>` as a **value oracle** to confirm and locate documents by the content of a f
CVE: CVE-2026-112954
- GHSA-vh56-5m88-39rqModerateApostropheCMS: GET /api/v1/@apostrophecms/page/:id bypasses viewRole restriction and exposes viewPermission-gated fields to lower-privileged users
< 4.34.0
### Summary The `getOne` REST endpoint for pages does not apply `viewRole` type-level restrictions or `viewPermission` field-level redaction. An editor-level user who knows a document `_id` can read the full content of admin-only pages and their privileged fields, while the `getAll` endpoint correctly enforces both restrictions. ### Details **Finding 3 — viewRole bypass:** `modules/@apostrophecms/page/index.js` exposes `getRestQuery(req, omitPermissionCheck = false)`. The `getAll` path calls `publicApiCheckAsync` and threads the full query builder pipeline including `viewRole` filtering. The
CVE: CVE-2026-112957
- GHSA-vmgj-9cwm-cf3hModerateApostropheCMS: Color field validation bypass allows stored XSS via HTML appended after rgb()/hsl() prefix
< 4.34.0
### Summary The color field accepts any string that *begins* with a valid `rgb()` / `hsl()` color, because `TinyColor.isValid` does not anchor its match at the end of the string. An authenticated user with edit rights can store arbitrary HTML after a valid color prefix. When the stored value is rendered , the payload executes, resulting in stored XSS. ### Details Server-side validation of the color field relies on TinyColor: ```js const { TinyColor } = require('@ctrl/tinycolor'); if (!new TinyColor(value).isValid) throw self.apos.error('invalid'); ``` TinyColor's `rgb`/`hsl` matchers are no
CVE: CVE-2026-112951
- GHSA-2j32-q6rx-h844ModerateApostropheCMS: Page REST API authorization bypass
< 4.33.0
**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` h
CVE: CVE-2026-110904
- GHSA-2jrp-qc93-h2j8ModerateApostropheCMS: Same-parent page reorder in @apostrophecms/page move() re-ranks a restricted parent's children without create authorization
< 4.33.0
## 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
CVE: CVE-2026-113655
- GHSA-653j-g8j7-gh54ModerateApostropheCMS: No Attempt Throttling on Authenticated Password-Change Endpoint Enables Unlimited Current-Password Guessing
< 4.33.0
**Summary** The self-service password-change endpoint verifies the current password but does not use the login module’s failed-attempt counter, throttle, delay, or lockout mechanism. An attacker with a valid authenticated session can submit repeated guesses for the current password without receiving a lockout or throttle response. **Exact source path** packages/apostrophe/modules/@apostrophecms/settings/index.js:621-664 **The password-change route is:** PATCH /api/v1/@apostrophecms/settings/password **The route calls the password verifier directly through:** await self.handleProtectedSubfor
CVE: CVE-2026-113710