GHSA-jqw5-w6h3-44g6
ApostropheCMS import-export: NoSQL injection via update-key value lets a crafted import match and overwrite an unidentified document
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| npm | @apostrophecms/import-export | < 3.7.0 | 3.7.0 |
Details
As written in the advisory (Markdown source, shown as plain text).
## 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.
References
External links from the advisory. They lead to third-party sites.
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-jqw5-w6h3-44g6
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
- https://github.com/apostrophecms/apostrophe
- https://github.com/apostrophecms/apostrophe/releases/tag/@apostrophecms/[email protected]