ApostropheCMS import-export: NoSQL injection via update-key value lets a crafted import match and overwrite an unidentified document
## Summary `@apostrophecms/import-export`'s update-by-key feature lets an imported document carry a `: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 id
- Products
- @apostrophecms/import-export
- Affected versions
- Not specified
- Fixed versions
- 3.7.0
- CVEs
- CVE-2026-113662
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 13:14 UTC · Revision 1
Source evidence
ApostropheCMS import-export: NoSQL injection via update-key value lets a crafted import match and overwrite an unidentified document
core-osv · Original sourcePublished 20 Oct 2026, 18:19 UTC
## Summary `@apostrophecms/import-export`'s update-by-key feature lets an imported document carry a `: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 id
osv:GHSA-jqw5-w6h3-44g6
@apostrophecms/import-export
Fixed versions: 3.7.0
Complete source record
{
"id": "f278d626cf1fe87189d683d37f7c28eebc1bf8f23429244d651214eeee6154b6",
"url": "https://osv.dev/vulnerability/GHSA-jqw5-w6h3-44g6",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "ApostropheCMS import-export: NoSQL injection via update-key value lets a crafted import match and overwrite an unidentified document",
"summary": "## Summary `@apostrophecms/import-export`'s update-by-key feature lets an imported document carry a `: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 id",
"evidence": "osv:GHSA-jqw5-w6h3-44g6",
"products": [
{
"name": {
"value": "@apostrophecms/import-export",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "3.7.0",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": []
}
],
"severity": "medium",
"structured": {
"id": "GHSA-jqw5-w6h3-44g6",
"aliases": [
"CVE-2026-113662",
"GHSA-jqw5-w6h3-44g6"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"
}
],
"affected": [
{
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "0"
},
{
"fixed": "3.7.0"
}
]
}
],
"package": {
"name": "@apostrophecms/import-export",
"purl": "pkg:npm/%40apostrophecms/import-export",
"ecosystem": "npm"
},
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-jqw5-w6h3-44g6/GHSA-jqw5-w6h3-44g6.json"
}
}
],
"modified": "2026-10-20T18:30:03.585162852Z",
"provider": "osv",
"references": [
"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/import-export@3.7.0"
],
"provider_metadata": {
"cwe_ids": [
"CWE-284",
"CWE-943"
],
"severity": "MODERATE",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-10-20T18:19:19Z"
}
},
"related_cves": [
{
"value": "CVE-2026-113662",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/severity/0/score"
},
"source_id": "core-osv",
"document_id": 390144,
"published_at": "2026-10-20T18:19:19+00:00"
}