Skip to content

fix(adapter-commons): validate query operators nested in arrays - #3700

Merged
marshallswain merged 10 commits into
dovefrom
fix/adapter-commons-array-query-validation
Aug 14, 2026
Merged

fix(adapter-commons): validate query operators nested in arrays#3700
marshallswain merged 10 commits into
dovefrom
fix/adapter-commons-array-query-validation

Conversation

@marshallswain

@marshallswain marshallswain commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary

  • validateQueryProperty() in @feathersjs/adapter-commons did not recurse into array values. _.isObject() in @feathersjs/commons excludes arrays, so isPlainObject() returned false and the value was returned unvalidated.
  • An extra array level around a sub-query bypassed the operator allow-list ($or: [[{ $where: '1==1' }]] was accepted; $or: [{ $where: '1==1' }] was correctly rejected). This is a v5 regression: v4 cleanQuery mapped over arrays.
  • The same allow-list also skipped filter values: non-array $or/$and, object $select (including { x: { $function: … } } for MongoDB $project), and object $sort values.
  • ObjectIdSchema accepted any object (additionalProperties: true), so generated MongoDB query schemas treated { _id: { $where } }, { $regex }, { $ne: null } as valid ids. Default validateQuery then skipped adapter sanitization.

filterQuery() is used by AdapterBase.sanitizeQuery(), so every adapter built on it is covered. ObjectIdSchema is tightened in both @feathersjs/schema and @feathersjs/typebox; the objectid keyword now accepts ObjectId instances and rejects other objects.

Not dependent on other PRs.

Other Information

Reported privately by Khiêm Trần (k0m3g4) and Chien Huynh (chienhm). Affects @feathersjs/adapter-commons 5.0.0–5.0.48 (and ObjectIdSchema / keywordObjectId on generated Mongo apps). v4 is not affected by the array regression.

On @feathersjs/memory the array wrap allowed sift $where through sanitization. Please publish a patch release after merge.

validateQueryProperty skipped array values because isPlainObject
excludes arrays. That let unknown $ operators through when wrapped
in an extra array level, e.g. $or: [[{ $where: '1==1' }]]. Recurse
into arrays so the operator allow-list applies at every level.
filterQuery left $select, $or, $and, and $sort object values
unvalidated, so unknown $ operators could ride through those
filters. ObjectIdSchema accepted any object, which let operator
documents pass querySyntax on generated MongoDB services.

Validate those filter values with validateQueryProperty. The
objectid keyword now accepts ObjectId instances and rejects
other objects, and ObjectIdSchema uses that for its object branch.
Tightening ObjectIdSchema dropped { _id: { $exists: true } }, which
only passed because the old any-object branch treated the operator
document as an id. Add $exists as a boolean operator on queryProperty
so it is allowed on every querySyntax field, including ObjectIds.
{ userId: { $ne: null } } is the usual Mongo and SQL "field is set"
query. After tightening ObjectIdSchema it only passed when the field
type already included null. Accept null on $ne/$in/$nin in
queryProperty so ObjectId, number, and string fields all allow it.
The default syntax already covers $ne/$in/$nin null and $exists.
Document how to add $like/$regex/array operators, how to allow
{ userId: null } on ObjectId query fields, and how $meta/$slice
map to service operators rather than querySyntax.
{ userId: null } is a real IS NULL query; accept null on the
queryProperty equality branch the same way as $ne/$in/$nin.

Allow $meta/$slice/$elemMatch inside object $select and $sort
(not as field operators). Add more with projectionOperators.

@feathersjs/mongodb defaults operators to include $regex and
$options. Generated Mongo query schemas do the same on the
primary string field.
Keep the allow-list tightenings. Remove $exists, implicit null
equality, default Mongo $regex, and projectionOperators so those
stay app-level extensions via querySyntax or operators, not new
supported syntax.
Assert $regex/$options, $like, $exists, $meta, and nullable
ObjectId fields still work when the app opts in, and that
unlisted operators such as $where stay rejected.
isObjectId now requires instanceof ObjectId, or _bsontype plus a
non-Object constructor and toHexString, so `{ _bsontype: 'ObjectId' }`
does not pass ObjectIdSchema. Attach $regex/$options to text in the
TypeBox Pick example.

@marshallswain marshallswain left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions addressed in b06311d: isObjectId now rejects spoofed { _bsontype: 'ObjectId' } JSON, and the TypeBox example attaches $regex/$options to text.

Comment thread packages/mongodb/src/converters.ts Outdated
Comment thread docs/api/schema/typebox.md Outdated
CI failed because people.create() returned no _id after earlier
aggregation tests mutated the shared service. Insert the fixture
through the collection so the test only covers validateQuery + $regex.
@marshallswain
marshallswain merged commit 304a1aa into dove Aug 14, 2026
4 checks passed
@marshallswain
marshallswain deleted the fix/adapter-commons-array-query-validation branch August 14, 2026 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant