GHSA-rqgm-cfrp-4hx8: Infoleak

Published Oct 21, 2026
·
Updated

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 field the projection was meant to hide, and - use ?sort[hiddenField]=1 as an ordering oracle to rank documents by a hidden field's value.

This is the same confidentiality-bypass class already patched for the choices/counts builders (gated by choicesFieldAllowedByProjection). That gate was never applied to search or sort.

Details For an anonymous caller, getRestQuery (piece types: modules/@apostrophecms/piece-type/index.js:1142; pages: modules/@apostrophecms/page/index.js) does:

js const query = self.find(req).attachments(true); query.applyBuildersSafely(req.query); // (1) apply ALL launderable builders from the query string ... query.project({ ...self.options.publicApiProjection, cacheInvalidatedAt: 1 }); // (2) force read projection query.set('publicApiProjection', self.options.publicApiProjection); // (3) used only by the choices/counts gate

applyBuildersSafely (modules/@apostrophecms/doc-type/index.js:3327) runs any builder that has a launder method. search and sort both have one (doc-type/index.js:2288 and 2127), so ?search= and ?sort[...]= are honored for anonymous requests.

Search vector. The search builder adds { $text: { $search: <user input> } } (doc-type/index.js:2282). The $text index covers highSearchText / lowSearchText / title / searchBoost (modules/@apostrophecms/doc/index.js:612). highSearchText / lowSearchText are built at save time by @apostrophecms/search:getSearchTexts → apos.schema.indexFields(schema, doc, texts) (modules/@apostrophecms/search/index.js:377), which indexes every schema field where searchable !== false (the default for string/text fields). publicApiProjection does not imply searchable: false. Step (2) narrows the output but not the $text filter, so the response returns the projected {title, slug, id} of exactly the documents whose hidden field contains the searched word.

Sort vector. finalizeSort (doc-type/index.js:3526) forwards raw user sort keys to MongoDB (it only rewrites fields flagged sortify: true). ?sort[hiddenField]=1 orders results by a projection-excluded field. Combined with perPage/page, an attacker can page through the fully ordered set.

Neither builder consults publicApiProjection, whereas choices/counts are gated at doc-type/index.js:2807 via choicesFieldAllowedByProjection.

PoC Preconditions: the piece/page type sets publicApiProjection, and its schema has at least one searchable (default) or sortable field not included in the projection.

Test type: article exposes only title, slug, id, but has hidden secret (integer) and secretNote (searchable string):

js export default { extend: '@apostrophecms/piece-type', options: { publicApiProjection: { title: 1, slug: 1, id: 1 } }, fields: { add: { secret: { type: 'integer' }, secretNote: { type: 'string' } // searchable by default; NOT in the projection } } };

Five articles were inserted and published via the authenticated REST API: A.secret=50, secretNote="zzsecretalpha"; B=10/zzsecretbravo; C=90/zzsecretcharlie; D=30/zzsecretdelta; E=70/zzsecretecho. The insert responses confirm the hidden secretNote value is written into the search index, e.g. for A: "highSearchText": "... zzsecretalpha", "lowSearchText": "... zzsecretalpha".

Anonymous requests (no credentials, no session):

bash [1] Baseline: projection hides secret/secretNote on direct read curl -s 'http://localhost:3000/api/v1/article?perPage=1'

[2] Search oracle: value exists ONLY in hidden secretNote curl -s 'http://localhost:3000/api/v1/article?search=zzsecretalpha' curl -s 'http://localhost:3000/api/v1/article?search=zznotpresentxyz'

[3] Sort oracle: order by hidden integer field curl -s 'http://localhost:3000/api/v1/article?sort%5Bsecret%5D=1' curl -s 'http://localhost:3000/api/v1/article?sort%5Bsecret%5D=-1'

Negative control: projection override is blocked curl -s 'http://localhost:3000/api/v1/article?project%5BsecretNote%5D=1&perPage=1'

Results (verified on ApostropheCMS 4.32.2, MongoDB 7.0):

| Request (anonymous) | Observed | Verdict | | ------------------------- | ------------------------------------------------- | ------------------------------------- | | GET /api/v1/article | id,title,slug,type,metaType,cacheInvalidatedAt | projection works on direct read | | ?search=zzsecretalpha | only Article A (with textScore) | leak: locates doc by hidden field | | ?search=zznotpresentxyz | 0 results | oracle confirmed | | ?sort[secret]=1 | B, D, A, E, C (= 10,30,50,70,90) | leak: full hidden ordering | | ?sort[secret]=-1 | C, E, A, D, B | reverse confirmed | | ?project[secretNote]=1 | secretNote absent | gated (projection override blocked) |

Impact Unauthenticated, network-reachable partial disclosure of fields the developer explicitly restricted with publicApiProjection:

- Search oracle: confirm and locate documents by words in hidden fields (e.g. internal notes, contact emails, draft metadata, moderation/status flags), returning the title/slug/id of matches. - Sort oracle: full ordering of documents by a hidden field. With one known reference value or a paging binary search, exact hidden values can be recovered.

No authentication or user interaction required. Not a full field dump, but a targeted oracle over exactly the data the projection is meant to conceal. Affects sites that configure publicApiProjection on piece types or pages, up to and including 4.32.2 (same code path present on current main).

Remediation Gate search, sort (and autocomplete, which feeds search) on the public API path the same way choices/counts are gated. When publicApiProjection is in force:

- sort: reject or drop any sort key not permitted by the projection, reusing choicesFieldAllowedByProjection. - search: restrict full-text search to fields the projection exposes (e.g. a projection-scoped search text), or refuse the search builder when the projection excludes any searchable field.

Affected Software

1 affected componentFixes available
npm/apostrophe<4.34.0
4.34.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/apostrophe to a version that resolves this vulnerability.

    Fixed in 4.34.0
  2. Compensating control

    On anonymous public API paths where publicApiProjection is configured, restrict full-text search to fields exposed by the projection, or refuse the search builder when the projection excludes any searchable field; apply the same gate to autocomplete, which feeds search, reusing choicesFieldAllowedByProjection.

  3. Compensating control

    On anonymous public API paths where publicApiProjection is configured, reject or drop any sort key not permitted by the projection, reusing choicesFieldAllowedByProjection.

Event History

Oct 21, 2026
Advisory Published
via GitHub·05:12 PM
Data Sourced
via GitHub·05:12 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Deployments are exposed where an ApostropheCMS module uses publicApiProjection to provide an anonymous read API. The issue affects public REST GET handlers for both piece types and pages when untrusted callers can supply query parameters.

2

What does an attacker need to exploit this?

No authentication or user interaction is required. An attacker can send search and sort query parameters to the anonymous endpoint; search can confirm or locate documents based on hidden-field content, while sort can order documents by hidden-field values.

3

Does this expose the hidden fields directly?

The documented impact is an information disclosure through inference rather than direct inclusion of hidden fields in the response. Search acts as a value oracle and sort acts as an ordering oracle against fields excluded by the projection.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203