Skip to content

Release Candidate: 7.6.2 - #6176

Merged
jamis merged 7 commits into
mongodb:7.6-stablefrom
jamis:updates-7.6
Sep 17, 2026
Merged

jamis merged 7 commits into
mongodb:7.6-stablefrom
jamis:updates-7.6

Conversation

@jamis

@jamis jamis commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

The MongoDB Ruby team is pleased to announce version 7.6.2 of the mongoid gem - a Ruby ODM for MongoDB. This is a new patch release in the 7.6.x series of Mongoid.

Install this release using RubyGems via the command line as follows:

gem install -v 7.6.2 mongoid

Or simply add it to your Gemfile:

gem 'mongoid', '7.6.2'

Have any feedback? Click on through to MongoDB's Jira and open a new ticket to let us know what's on your mind.

🔴 End-Of-Life Notice

After December 31, 2026, this 7.x series will no longer be supported. It will no longer receive any maintenance or security updates. If you are still running on Mongoid 7.x or earlier, please upgrade.

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) 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)

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 7.6. 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.
…7.6 backport)

Backport 5993 to 7.6, 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.
…port)

Backport the MONGOID-5994 operator guard to 7.6. 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, 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 all
files except the deleted spec/integration/dots_and_dollars_spec.rb (absent
from 7.6) merged cleanly.

Squashed commit of the following:

commit 368f0b2f78b946a6ecaae002f2c347f7e3994dc4
Author: Jamis Buck <jamis.buck@mongodb.com>
Date:   Fri Sep 11 09:02:47 2026 -0600

    MONGOID-5994 Guard elem_match and chained overrides against JS operator smuggling (7.6)

    The 5994 guard lives in _mongoid_expand_keys, but on this branch elem_match
    (and every other entry point that routes through Mergeable#__override__ --
    exists, eq, gt, in, ne, geo, etc.) writes to the selector directly and never
    passes through the funnel. Under the default overwrite_chained_operators=true
    setting, elem_match(a: { '$where' => js }) therefore bypassed strict mode.

    Enforce _mongoid_validate_operators! at the top of __override__ so the
    chained-override family is covered too. Master closes this gap by routing
    elem_match through and_with_operator (MONGOID-5509/5330 removed the alternate
    overwrite mode entirely), which is too invasive to backport wholesale; this is
    the minimal equivalent for 7.6.

commit fa74842d157382f23b7b40f3df5d30a2fc85ba02
Author: Jamis Buck <jamis.buck@mongodb.com>
Date:   Fri Sep 11 08:39:12 2026 -0600

    MONGOID-5994 Reject JavaScript query operators at any depth (7.6 backport)

    Backport the MONGOID-5994 operator guard to 7.6. 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, 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 all
    files except the deleted spec/integration/dots_and_dollars_spec.rb (absent
    from 7.6) merged cleanly.

    Squashed commit of the 8.0 backport (a48a33c).
@jamis
jamis requested review from Jibola and a lite review from Copilot September 17, 2026 19:17
@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

Critical query-validation, regexp-budget, and unsafe field-access issues remain unresolved, along with lost CI coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Release candidate for Mongoid 7.6.2, adding regexp limits, query-operator safeguards, safer in-memory access, and nested-attribute handling.

Changes:

  • Adds cumulative in-memory regexp budgets and timeout errors.
  • Rejects unsafe JavaScript query operators by default.
  • Restricts nested-attribute reparenting and improves field access safety.
  • Updates release metadata, tests, fixtures, and Evergreen configuration.
File summaries
File Reviewed change
spec/mongoid/matcher/regexp_budget_spec.rb Regexp budget coverage.
spec/mongoid/criteria/queryable/selectable_where_spec.rb Query guard coverage.
spec/mongoid/criteria/queryable/selectable_logical_spec.rb Logical query and string-query updates.
spec/mongoid/criteria_spec.rb Criteria compatibility coverage.
spec/mongoid/contextual/memory_spec.rb Safe in-memory field access coverage.
spec/mongoid/contextual/aggregable/memory_spec.rb Safe aggregation coverage.
spec/mongoid/attributes/nested_spec.rb Nested-attribute behavior coverage.
spec/mongoid/association/referenced/has_many/proxy_spec.rb Association removal coverage.
spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb HABTM removal coverage.
spec/integration/query_operator_guard_spec.rb Query-operator guard integration coverage.
spec/integration/matcher_regexp_timeout_spec.rb Regexp timeout integration coverage.
spec/integration/matcher_operator_data/regex.yml Regexp matcher fixtures.
spec/integration/app_spec.rb JSON dependency compatibility.
product.yml Product release metadata.
lib/mongoid/warnings.rb Reparenting warning.
lib/mongoid/version.rb Version 7.6.2 metadata.
lib/mongoid/threaded.rb Moderate (2 votes): regexp budget thread-local key is not namespaced.
lib/mongoid/matcher/regexp_budget.rb Critical (2 votes): caller regexp timeouts are not preserved. Moderate (2 votes): the budget is not checked after the final charge.
lib/mongoid/matcher/regex.rb Budgeted regexp matching.
lib/mongoid/matcher/eq_impl_with_regexp.rb Budgeted regexp equality matching.
lib/mongoid/matcher.rb Matcher loading.
lib/mongoid/matchable.rb Budgeted document matching.
lib/mongoid/field_readable.rb Safe field resolution.
lib/mongoid/errors/in_memory_regexp_timeout.rb In-memory regexp timeout error.
lib/mongoid/errors.rb Error loading.
lib/mongoid/criteria/queryable/selectable.rb Critical (3 votes): multi-criteria any_of can bypass unsafe-operator validation. Moderate (1 vote): string where changes the expected embedded-query error.
lib/mongoid/criteria/queryable/mergeable.rb Query validation integration.
lib/mongoid/contextual/memory.rb Critical (1 vote): the default legacy pluck path can still dispatch methods such as destroy.
lib/mongoid/contextual/aggregable/memory.rb Safe aggregation reads.
lib/mongoid/config.rb Nit (1 vote): documents unavailable none_of coverage. Nit (1 vote): Ruby-version wording misstates JRuby timeout behavior.
lib/mongoid/association/referenced/has_many/proxy.rb Bounded association removal.
lib/mongoid/association/nested/nested_buildable.rb Scalar nested ID validation.
lib/mongoid/association/nested/many.rb Moderate (1 vote): absent embedded IDs with _destroy can raise instead of being ignored.
lib/mongoid/association/depending.rb Dependency owner attribute handling.
lib/config/locales/en.yml Timeout messages.
.evergreen/config/variants.yml.erb Moderate (3 votes): disabled matrices remove active application-test variants.
.evergreen/config.yml Moderate (2 votes): disabled matrices remove active application-test variants.
Review details

Suppressed comments (5)

lib/mongoid/association/nested/many.rb:185

  • Because the association.embedded? branch runs before destroyable?, nested attributes containing _destroy for an id that is absent from an embedded association raise DocumentNotFound instead of being ignored. The referenced-association branch below already implements the documented ignore behavior; test this case and handle destroyable? before the embedded-association not-found branch.
          elsif association.embedded?
            raise Errors::DocumentNotFound.new(association.klass, id)
          elsif destroyable?(attrs)
            # A destroy of a document that is not in the association is
            # ignored, rather than reaching for it outside the association.

lib/mongoid/config.rb:151

  • none_of is not defined in this 7.6 branch—the new spec notes that it is unavailable at spec/mongoid/criteria/queryable/selectable_where_spec.rb:720—so this documentation promises coverage for an API callers cannot invoke. Remove it from this list or add the API before documenting it.
    # +where+, +find_by+, +and+, +or+, +nor+, +not+, +any_of+, and +none_of+.

lib/mongoid/config.rb:136

  • PER_REGEXP_TIMEOUT is deliberately false for JRuby even when it reports Ruby 3.4, so this 'Ruby 3.2 and later' description is false there: the implementation uses the whole-scope Timeout wall-clock fallback. Document the engine capability instead so JRuby users know what the limit covers.
    # Set to nil to remove the limit. On Ruby 3.2 and later the remaining
    # budget is compiled into the pattern, so the limit counts only the time
    # spent matching. Earlier Rubies have no per-Regexp timeout, so the query
    # is bounded with Timeout instead and the limit is wall clock over the
    # whole in-memory evaluation.

lib/mongoid/criteria/queryable/selectable.rb:906

  • The guard also misses public selector-building paths that do not use _mongoid_expand_keys: geo_spatial writes through __merge__, and max_distance writes through __add__/with_strategy. In strict mode, for example, Band.geo_spatial('$where' => js) can still add a server-side JavaScript operator; validate these paths or move the check to a common selector write point.
        # It deliberately does not cover the APIs that ask for JavaScript
        # outright, such as #js_query and Criteria#for_js: there the developer
        # has chosen server-side JavaScript, so there is nothing to guard
        # against. The same goes for the low-level Storable methods
        # (#add_field_expression, #add_operator_expression), which write to the
        # selector directly.

lib/mongoid/criteria/queryable/selectable.rb:820

  • This guard now rejects string where criteria while they are being built, so match.records.where("this.name == null") raises Mongoid::Errors::InvalidQuery before the embedded matcher can raise its existing UnsupportedJavascript. The unchanged example at spec/mongoid/criteria_spec.rb:3690-3694 will fail; update that expectation/configuration (or preserve the old embedded-specific error) as part of this change.
              unless Mongoid.allow_unsafe_query_operators?
                raise Errors::InvalidQuery,
                      "String criteria are not allowed because they compile to the '$where' operator, " \
                      'which is not allowed in a query expression. Set Mongoid.allow_unsafe_query_operators = true ' \
                      'to permit all operators.'
  • Files reviewed: 39/39 changed files
  • Comments generated: 7
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

end
meth = klass.aliased_associations[segment] || segment
res.nil? ? document.try(meth) : res
res.nil? ? read_field_value(document, segment) : res
Comment on lines +896 to +899
# This is called from #_mongoid_expand_keys rather than from the
# individual query methods, because that is the one point every query
# method taking a user-supplied expression passes through on its way to
# the selector.
def bake(regexp)
return regexp unless PER_REGEXP_TIMEOUT

::Regexp.new(regexp.source, regexp.options, timeout: @timeout)
Comment thread .evergreen/config.yml
Comment on lines +765 to +769
# - matrix_name: app-tests-rails-7
# matrix_spec:
# ruby: ["ruby-3.2", "ruby-3.3"]
# driver: ["current"]
# mongodb-version: '7.0'
Comment on lines +114 to +118
# - matrix_name: app-tests-rails-7
# matrix_spec:
# ruby: ["ruby-3.2", "ruby-3.3"]
# driver: ["current"]
# mongodb-version: '7.0'
Comment on lines +301 to +303
ensure
budget.charge(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started)
end
Comment thread lib/mongoid/threaded.rb
end

# The key for the time left in the current in-memory regexp budget.
REGEXP_BUDGET_KEY = 'regexp-budget'
@jamis
jamis merged commit af308fa into mongodb:7.6-stable Sep 17, 2026
30 checks passed
@jamis
jamis deleted the updates-7.6 branch September 17, 2026 22:21
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