GHSA-jqw5-w6h3-44g6
ApostropheCMS import-export: NoSQL injection via update-key value lets a crafted import match and overwrite an unidentified document
At a glance
- Published
- Updated
- CVE
- CVE-2026-113662
- CVSS_V3
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
Description
## Summary `@apostrophecms/import-export`'s update-by-key feature lets an imported document carry a `<field>:key` property (for example `slug:key`) whose value is used to find an existing document to update instead of inserting a new one. `getUpdateKey()`/`insertOrUpdateDocWithKey()` (`lib/methods/import.js`) use that value directly as a MongoDB query value with no laundering: ```js const matchingDraftPromise = manager.findForEditing( req.clone({ mode: 'draft' }), { aposMode: 'draft', [updateField]: doc[updateKey] }).toObject(); ``` Archive documents are parsed with EJSON (see `lib/formats/gzip.js`), which revives objects such as `{ $ne: null }` as live values, the same untrusted data source and revival mechanism already fixed once in this module for `findSingletonAposDocId`/`findParkedAposDocId` in GHSA-79qf-vqgc-7xx3. That fix did not touch `insertOrUpdateDocWithKey`, so the identical class of bug survives here: a crafted `"slug:key": { "$ne": null }` entry in an archive's `aposDocs.json` makes the import match (and then update) an existing document the archive's author never actually identified, instead of failing to find a match. ## Impact Reaching this requires only an authenticated session with import access to the target type (the built-in contributor role holds this, matching the precondition of GHSA-79qf-vqgc-7xx3), and no admin interaction. Because the match uses a MongoDB operator instead of an exact value, it is not limited to a document the importer already knows the identifying field of: any existing document the importer has generic edit rights on and that satisfies the operator (which `{ $ne: null }` trivially does for any doc with a non-null value in that field) is a candidate, and Apostrophe's `findForEditing` permission filter, not the update-key logic, is the only thing narrowing which document actually gets matched and overwritten. Live-verified: a contributor-role request importing a document containing `"slug:key": { "$ne": null }` had its content applied to an existing, unrelated draft article, overwriting its title, while the importer never specified that article's real slug. ## Affected versions Confirmed on `main` branch HEAD as of 2026-09-04, `packages/import-export/lib/methods/import.js`. ## Suggested fix Launder `doc[updateKey]` to a plain string (mirroring the fix already applied to `type`/`aposLocale`/`parkedId` in GHSA-79qf-vqgc-7xx3) before using it as a query value, and fall back to the insert path when it is not a usable string.
Affected packages
| Package | Versions |
|---|---|
| npm · @apostrophecms/import-export | introduced 0, fixed 3.7.0 |
References
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-jqw5-w6h3-44g6 WEB
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce WEB
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350 WEB
- https://github.com/apostrophecms/apostrophe PACKAGE
- https://github.com/apostrophecms/apostrophe/releases/tag/@apostrophecms/import-export@3.7.0 WEB
Sources
| Source | Licence |
|---|---|
| OSV.dev | CC-BY 4.0 — GitHub Advisory Database |