Release Candidate: 8.0.13 - #6175
Merged
Merged
Conversation
…names as method calls Backport to the 8.0 backport branch.
Backport of the security fix that limits how long one in-memory query may spend executing regular expressions. Adds Matcher::RegexpBudget, a configurable limit bounded across embedded-association queries, association removal, and Document#_matches?.
Backport of the security fix to 8.0. An id in nested attributes was resolved against the parent's association, and on a miss it fell back to a collection-wide unscoped lookup that dropped default scopes. The matched document could then be mass assigned and pushed into the caller's association, letting a user overwrite someone else's document. The id is now resolved against the association's class. Non-scalar ids (operator hashes, arrays) are rejected. Any id that is not already in the association raises a DocumentNotFound error, and a destroy of a document that is not in the association is ignored. Introduces allow_reparenting_via_nested_attributes (defaulting to false, from MONGOID-5930) as an upgrade path for applications that relied on reparenting. Enabling it restores the previous behavior behind a one-time deprecation warning.
…8.0 backport) Backport 5993 to 8.0, along with the prerequisite pieces of MONGOID-5939 it depends on: the allow_unsafe_query_operators config option, the expr_query allowlist guard, and the string-form guard on Selectable#where. Skipped 5939's unrelated Gemfile change. Resolved a config.rb conflict where 8.0 has the allow_reparenting_via_nested_attributes option where 8.1 had the #config singleton method.
…port)
Backport the MONGOID-5994 operator guard to 8.0. The guard now runs in
_mongoid_expand_keys, the one point every user-supplied query expression
passes through, so and, or, nor, not, any_of, none_of, and elem_match
can no longer smuggle in $where. Nested forms such as
{'$expr' => {'$function' => ...}} and {'$or' => [{'$where' => ...}]}
are also rejected. allow_unsafe_query_operators now defaults to false.
Prerequisite work from MONGOID-5939 shipped with the 5993 backport, so
only config.rb required a manual merge, resolving the same
allow_reparenting_via_nested_attributes context conflict as 5993.
Squashed commit of the following:
commit af8ded5d7818434a9c0e960cee89030629e50163
Author: Jamis Buck <jamis.buck@mongodb.com>
Date: Wed Sep 9 16:49:43 2026 -0600
MONGOID-5994 Drop none_of case from 8.0 backport spec
The 8.1 reference spec tests that the operator guard rejects $where
smuggled through none_of. Mongoid 8.0 does not have the none_of query
method (MONGOID-5453 landed in 8.1), so this case failed with NoMethodError
rather than exercising the guard. Remove the inapplicable case; every
entry point 8.0 has (where, find_by, and, or, nor, not, any_of,
elem_match) still routes through _mongoid_expand_keys and remains covered.
commit 2243b9fb1369a3066086530df8f8403f356da76f
Author: Jamis Buck <jamis.buck@mongodb.com>
Date: Wed Sep 9 15:34:30 2026 -0600
MONGOID-5994 Reject JavaScript query operators at any depth (8.0 backport)
Backport the MONGOID-5994 operator guard to 8.0. The guard now runs in
_mongoid_expand_keys, the one point every user-supplied query expression
passes through, so and, or, nor, not, any_of, none_of, and elem_match
can no longer smuggle in $where. Nested forms such as
{'$expr' => {'$function' => ...}} and {'$or' => [{'$where' => ...}]}
are also rejected. allow_unsafe_query_operators now defaults to false.
Prerequisite work from MONGOID-5939 shipped with the 5993 backport, so
only config.rb required a manual merge, resolving the same
allow_reparenting_via_nested_attributes context conflict as 5993.
…or smuggling (8.0)
The 5994 guard lives in _mongoid_expand_keys, but elem_match (and every other
entry point routed through Mergeable#__override__ -- exists, eq, gt, in, ne,
geo, etc.) writes to the selector directly and never passes through the
funnel. With overwrite_chained_operators set to true (the pre-8.0 default,
still active for applications that enable the legacy behavior), elem_match(a:
{ '$where' => js }) therefore bypasses strict mode.
Enforce _mongoid_validate_operators! at the top of __override__, matching the
7.6 backport. The default suite does not exercise this path (the flag
defaults to false on 8.0, routing elem_match through and_with_operator), so
no test coverage is added here.
The existing elem_match guard case runs under the branch default of overwrite_chained_operators=false, so it exercises and_with_operator, not the Mergeable#__override__ path that the guard was added to. Add a case that forces the flag true, asserting elem_match with a nested $where is rejected under strict mode, that an ordinary value still works, and that opting out restores the old behavior.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain in the in-memory field access, regex budgeting, and nested association handling.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Mongoid 8.0.13 hardens in-memory regex evaluation, unsafe query handling, nested attributes, and dynamic field access.
Changes:
- Adds regex budgets and timeout errors.
- Guards unsafe query operators and scopes nested-attribute lookups.
- Updates association behavior, tests, configuration, and release metadata.
File summaries
| File | Reviewed changes / findings |
|---|---|
spec/mongoid/matcher/regexp_budget_spec.rb |
Tests regex budgets and timeout behavior. |
spec/mongoid/criteria/queryable/selectable_where_spec.rb |
Tests query operator guards. |
spec/mongoid/criteria/queryable/selectable_logical_spec.rb |
Updates logical query coverage. |
spec/mongoid/criteria_spec.rb |
Updates query operator coverage. |
spec/mongoid/contextual/memory_spec.rb |
Tests safe in-memory field reads. |
spec/mongoid/contextual/aggregable/memory_spec.rb |
Tests safe aggregate access. |
spec/mongoid/attributes/nested_spec.rb |
Tests nested ID and reparenting behavior. |
spec/mongoid/association/referenced/has_many/proxy_spec.rb |
Tests bounded association removal. |
spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb |
Tests HABTM behavior. |
spec/integration/query_operator_guard_spec.rb |
Tests query guards end to end. |
spec/integration/matcher_regexp_timeout_spec.rb |
Tests regex limits end to end. |
spec/integration/matcher_operator_data/regex.yml |
Adds regex matching cases. |
spec/integration/dots_and_dollars_spec.rb |
Updates unsafe-field behavior. |
spec/integration/associations/has_and_belongs_to_many_spec.rb |
Tests reparenting configuration. |
spec/integration/app_spec.rb |
Pins JSON compatibility. |
product.yml |
Updates release metadata. |
lib/mongoid/warnings.rb |
Adds reparenting warning. |
lib/mongoid/version.rb |
Sets version 8.0.13. |
lib/mongoid/threaded.rb |
Adds regex budget storage. |
lib/mongoid/matcher/regexp_budget.rb |
Implements regex budgets. Moderate (1 vote): preserves neither stricter pattern timeouts nor their cache key. Moderate (1 vote): misses Set traversal. Moderate (1 vote): lacks a final exhaustion check. |
lib/mongoid/matcher/regex.rb |
Routes regex matching through budgets. |
lib/mongoid/matcher/eq_impl_with_regexp.rb |
Updates regex equality matching. |
lib/mongoid/matcher.rb |
Loads regex budgeting. |
lib/mongoid/matchable.rb |
Budgets document matching. |
lib/mongoid/field_readable.rb |
Adds safe field lookup. |
lib/mongoid/errors/in_memory_regexp_timeout.rb |
Adds timeout error class. |
lib/mongoid/errors.rb |
Loads the new error. |
lib/mongoid/criteria/queryable/selectable.rb |
Adds operator validation. |
lib/mongoid/criteria/queryable/mergeable.rb |
Guards chained operator paths. |
lib/mongoid/contextual/memory.rb |
Applies safe reads and scan budgets. Critical (2 votes): legacy distinct and pluck paths still dispatch methods directly. |
lib/mongoid/contextual/aggregable/memory.rb |
Uses safe field access for aggregation. |
lib/mongoid/config.rb |
Adds configuration options. Nit (3 votes): documents none_of coverage although the 8.0 branch lacks that API. |
lib/mongoid/association/referenced/has_many/proxy.rb |
Bounds conditional removals. |
lib/mongoid/association/nested/nested_buildable.rb |
Rejects non-scalar IDs. |
lib/mongoid/association/nested/many.rb |
Scopes nested association resolution. Moderate (1 vote): an absent embedded ID with allowed _destroy raises DocumentNotFound before the destroy check. |
lib/mongoid/association/depending.rb |
Renames dependency ownership metadata. |
lib/config/locales/en.yml |
Documents timeout errors. |
.evergreen/config/variants.yml.erb |
Updates Evergreen variants. |
.evergreen/config.yml |
Updates generated Evergreen configuration. |
Review details
Suppressed comments (4)
lib/mongoid/association/nested/many.rb:184
- For an embedded association, this branch raises before
destroyable?(attrs)is evaluated. An allowed_destroyfor an id absent from the embedded association therefore raisesDocumentNotFoundinstead of being ignored, contrary to the missing-destroy behavior implemented below and described for this release. Evaluate the destroy case before the embedded-association not-found error.
elsif association.embedded?
raise Errors::DocumentNotFound.new(association.klass, id)
elsif destroyable?(attrs)
lib/mongoid/matcher/regexp_budget.rb:139
- On MRI, this rebuilds every incoming
Regexpwith only@timeout, so it discards a stricter per-pattern timeout. For example,Regexp.new(..., timeout: 0.01)is allowed to run for the 5-second Mongoid budget when used insideopen, even though the code deliberately preserves a stricter globalRegexp.timeoutabove. Preserve the minimum of the pattern, global, and budget limits, and include the pattern timeout in the cache key so cached patterns cannot bypass that constraint.
::Regexp.new(regexp.source, regexp.options, timeout: @timeout)
lib/mongoid/matcher/regexp_budget.rb:365
- This traversal only descends through
HashandArray, but Mongoid's query extensions supportSetvalues and leave them as sets during selector expansion. A selector such as{ title: { '$in' => Set[/evil/] } }therefore reaches the in-memory matcher withoutlimit_forseeing the regexp, bypassing the new budget. Traverse every supported collection type (at leastSet) or normalize the selector before scanning it.
when Array
object.any? { |v| contains_regexp?(v) }
else
lib/mongoid/matcher/regexp_budget.rb:303
- On the per-Regexp-timeout path, this only charges the elapsed time and does not check exhaustion after the final match. If the only match spends the remaining budget compiling the pattern (which this implementation intentionally charges) and then matches quickly,
openreturns normally and noInMemoryRegexpTimeoutis raised even though the limit was exceeded. Check exhaustion after charging or when closing the outer scope, while preserving the already-rescued timeout cause.
ensure
budget.charge(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started)
end
- Files reviewed: 39/39 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| include Association::EagerLoadable | ||
| include Queryable | ||
| include Positional | ||
| include FieldReadable |
| # including inside +$expr+ and the logical operators. | ||
| # | ||
| # This applies to every query method that accepts an expression, including | ||
| # +where+, +find_by+, +and+, +or+, +nor+, +not+, +any_of+, and +none_of+. |
Matches master's pin. The pinned 14cc285a selects the ASAN/UBSAN-instrumented Python 3.14 via find_python3 on refreshed ubuntu2404 images; LeakSanitizer then aborts the mongo-orchestration bootstrap on pymongo _cmessage import-time leaks, failing every test task on that matrix before specs run. 890a93b (DRIVERS-3564 uv rework) builds the orchestration venv from 'python' on PATH instead of the /opt/python version scan, avoiding the sanitizer build.
Jibola
approved these changes
Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The MongoDB Ruby team is pleased to announce version 8.0.13 of the
mongoidgem - a Ruby ODM for MongoDB. This is a new patch release in the 8.0.x series of Mongoid.Install this release using RubyGems via the command line as follows:
Or simply add it to your
Gemfile:Have any feedback? Click on through to MongoDB's Jira and open a new ticket to let us know what's on your mind.
Bug Fixes
Bound regular-expression execution time in in-memory queries (MONGOID-5981)
Queries evaluated in memory, such as those against embedded associations, run in the calling thread and can spend an unbounded amount of CPU matching regular expressions. A single in-memory query is now limited by
Mongoid.in_memory_regexp_time_limit(default5.0seconds); exceeding the limit raisesMongoid::Errors::InMemoryRegexpTimeout.Resolve nested attribute ids within the caller's association (MONGOID-5992)
An id given in nested attributes is now resolved within the association the attributes are being applied to, rather than falling back to a collection-wide lookup that ignored default scopes. A document that is not part of that association raises
Mongoid::Errors::DocumentNotFound, and a request to destroy a document that is not in the association is ignored. The newMongoid.allow_reparenting_via_nested_attributesoption (defaultfalse) restores the previous reparenting behavior when set totrue.Reject the string form of where under the query operator guard (MONGOID-5993)
A String passed to
#whereis sent to MongoDB as a$whereexpression. This now raisesMongoid::Errors::InvalidQuerywhenMongoid.allow_unsafe_query_operatorsisfalse(the default); the string form is allowed only when that option is enabled.Reject JavaScript query operators at any depth (MONGOID-5994)
Mongoid.allow_unsafe_query_operatorsnow defaults tofalse. When it isfalse, the$where,$function, and$accumulatoroperators are rejected anywhere in a query selector. The guard covers every criterion-building method (where,find_by,or,and,nor,not,any_of,none_of, andelem_match, including chained operator overrides) and inspects nested expressions such as{'$expr' => {'$function' => ...}}in full.Other Bug Fixes