@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:
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.
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.
Confirmed on main branch HEAD as of 2026-09-04,
packages/import-export/lib/methods/import.js.
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.
{
"cwe_ids": [
"CWE-284",
"CWE-943"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-20T18:19:19Z",
"nvd_published_at": null,
"severity": "MODERATE"
}