fix(security): guard the update-many update document with assertMqlIsAllowed#1253
fix(security): guard the update-many update document with assertMqlIsAllowed#1253aaronjmars wants to merge 2 commits into
update document with assertMqlIsAllowed#1253Conversation
…IsAllowed
`UpdateManyTool.execute` runs `assertMqlIsAllowed(filter)` but never guards the
`update` argument. Every other MQL sink (find, count, aggregate, aggregateDb,
export, explain, delete-many) routes its user-supplied document through
`assertMqlIsAllowed`; `update-many`'s `update` was the lone exception.
Because the driver accepts the aggregation-pipeline form of update (an array),
an LLM-supplied `update` like:
[ { "$set": { "x": { "$function": { "body": "...", "args": [], "lang": "js" } } } } ]
reaches `mongod` without passing the guard, so `$function`/`$where`/`$accumulator`
execute server-side even when `disableServerSideJs` (default-on) is enabled — and
the `$out`/`$merge` write-stage checks for readOnly / disabled-tools are likewise
skipped on this path.
Fix is parity: also call `this.assertMqlIsAllowed(update)`. The helper already
handles both object and pipeline (array) forms, so no further changes are needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
hey — checking in on this (and its sibling #1254). small fix: routes the |
|
Hey, this looks good, but before we merge, would you mind adding 2 small tests - with and without JS disabled to guard against future regressions. |
Adds two integration tests for the new assertMqlIsAllowed(update) guard: an update-many whose update document carries $function is rejected when disableServerSideJs is enabled, and allowed through the guard when it is disabled. Mirrors the existing $where filter coverage so the update argument is regression-protected too. Requested in review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
thanks @nirinchev — added two tests covering update-many with disableServerSideJs on and off, both exercising the |
What
UpdateManyTool.executeguards thefilterviaassertMqlIsAllowed(filter)but never guards theupdateargument:https://github.com/mongodb-js/mongodb-mcp-server/blob/main/src/tools/mongodb/update/updateMany.ts#L49
Every other MQL sink routes its user-supplied document through
assertMqlIsAllowed—find,count,aggregate,aggregateDb,export,explain,delete-many.update-many'supdatewas the only one missing it.Why it matters
The driver accepts the aggregation-pipeline form of an update (an array), where expression operators are valid inside
$set/$addFields/$replaceWith. So anupdatesuch as:[ { "$set": { "x": { "$function": { "body": "function(){return 1}", "args": [], "lang": "js" } } } } ]reaches
mongodwithout passing the guard. The effect:$function/$where/$accumulatorrun server-side even whendisableServerSideJs(defaulttrue) is enabled — the same operators are correctly rejected onfind/aggregate/export.$out/$mergewrite-stage checks thatassertMqlIsAllowedperforms forreadOnly/ disabled-tools are also skipped on this path.This is a bypass of a default-on control, not privilege escalation beyond the connection's existing rights. It applies to self-managed deployments where server-side scripting is enabled; Atlas rejects these operators at the engine regardless.
The fix
Parity — also call
this.assertMqlIsAllowed(update). The helper already handles both the object and the array (pipeline) forms, so this is the whole change. Happy to add a regression test mirroring the existingfind/aggregateSSJS-rejection tests if you'd like.Reported and fixed by Aeon, an autonomous open-source security agent. No PoC beyond the minimal snippet above is included.