Skip to content

Release Candidate: 8.0.13 - #6175

Merged
jamis merged 10 commits into
mongodb:8.0-stablefrom
jamis:updates-8.0
Sep 17, 2026
Merged

jamis merged 10 commits into
mongodb:8.0-stablefrom
jamis:updates-8.0

Conversation

@jamis

@jamis jamis commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

The MongoDB Ruby team is pleased to announce version 8.0.13 of the mongoid gem - 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:

gem install -v 8.0.13 mongoid

Or simply add it to your Gemfile:

gem 'mongoid', '8.0.13'

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 (default 5.0 seconds); exceeding the limit raises Mongoid::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 new Mongoid.allow_reparenting_via_nested_attributes option (default false) restores the previous reparenting behavior when set to true.

Reject the string form of where under the query operator guard (MONGOID-5993)

A String passed to #where is sent to MongoDB as a $where expression. This now raises Mongoid::Errors::InvalidQuery when Mongoid.allow_unsafe_query_operators is false (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_operators now defaults to false. When it is false, the $where, $function, and $accumulator operators 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, and elem_match, including chained operator overrides) and inspects nested expressions such as {'$expr' => {'$function' => ...}} in full.

Other Bug Fixes

  • In-memory queries, such as those executed against embedded associations, now read field names from the query as data rather than dispatching them as method calls on the document (MONGOID-5973)

…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.
@jamis
jamis requested a review from Jibola September 17, 2026 19:13
@jamis
jamis requested a review from a team as a code owner September 17, 2026 19:13
Copilot AI lite review requested due to automatic review settings September 17, 2026 19:13
@jamis jamis added the release-candidate The PR represents a potential candidate for a new release label Sep 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 _destroy for an id absent from the embedded association therefore raises DocumentNotFound instead 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 Regexp with 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 inside open, even though the code deliberately preserves a stricter global Regexp.timeout above. 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 Hash and Array, but Mongoid's query extensions support Set values and leave them as sets during selector expansion. A selector such as { title: { '$in' => Set[/evil/] } } therefore reaches the in-memory matcher without limit_for seeing the regexp, bypassing the new budget. Traverse every supported collection type (at least Set) 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, open returns normally and no InMemoryRegexpTimeout is 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
Comment thread lib/mongoid/config.rb
# 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.
@jamis
jamis merged commit 4775b2a into mongodb:8.0-stable Sep 17, 2026
33 checks passed
@jamis
jamis deleted the updates-8.0 branch September 17, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-candidate The PR represents a potential candidate for a new release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants