Skip to content

Release Candidate: 9.0.12 - #6173

Merged
jamis merged 11 commits into
mongodb:9.0-stablefrom
jamis:updates-9.0
Sep 17, 2026
Merged

jamis merged 11 commits into
mongodb:9.0-stablefrom
jamis:updates-9.0

Conversation

@jamis

@jamis jamis commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

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

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

gem install -v 9.0.12 mongoid

Or simply add it to your Gemfile:

gem 'mongoid', '9.0.12'

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.

Fix encryption schema generation for automatic encryption (MONGOID-5984) (MONGOID-5989)

Automatic encryption schema generation no longer loops on models that embed themselves, and it now handles polymorphic embeds_one relations and embedded schemas that carry their own encryptMetadata correctly. A model whose collection has no entry in the generated schema is no longer written without encryption: such a write raises Mongoid::Errors::NoEncryptionSchema instead of storing the field in plaintext.

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 Mongoid.allow_reparenting_via_nested_attributes option now defaults to false; set it to true to restore the previous reparenting behavior.

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)

…names as method calls

Backport to the 9.0 backport branch.
Squashed commit of the following:

commit ba00b73d21ac69426f5446b04f28fa6f19e442a7
Author: Jamis Buck <jamis.buck@mongodb.com>
Date:   Mon Sep 7 12:35:11 2026 -0600

    MONGOID-5981 Cap the app-test json gem at 2.x for Rails compatibility

    json 3.0 removed the quirks_mode and max_nesting keywords that
    ActiveSupport::JSON (every 7.x) still passes to JSON.generate and
    JSON.parse. The app-test harness generates a Rails app with a fresh
    bundle, so on a machine where the newest json is 3.x every generated
    app fails to encode any response with ArgumentError: unknown keyword:
    quirks_mode. Pin json < 3 in the generated app's Gemfile (adjust_app_gemfile)
    so the bundle resolves a 2.x json that ActiveSupport still accepts.

    Incidental to MONGOID-5981; needed so the backport's app tests stay green.

commit 801b4e53d5b02d65acaf0a48aa5d3057de631523
Author: Jamis Buck <jamis.buck@mongodb.com>
Date:   Mon Sep 7 11:34:42 2026 -0600

    MONGOID-5981 Mark open no-budget scopes with a sentinel

    RegexpBudget opened a scope with nothing to bound by storing nil under
    Threaded::REGEXP_BUDGET_KEY, relying on Threaded.has? treating a present
    nil key as present. On the 9.0 backport branch Threaded.has? is
    !get(key).nil?, so a nil value reads as absent and a nested scope
    re-decided for itself instead of joining the enclosing no-budget scope.

    Store a NO_BUDGET sentinel object instead, which any Threaded.has? reads
    as present, and map it back to nil in RegexpBudget.current.

commit d9a621bac4670682c4be628eefe7be88c7258f60
Author: Jamis Buck <jamis.buck@mongodb.com>
Date:   Mon Sep 7 10:57:34 2026 -0600

    MONGOID-5981 Bound regex execution in the in-memory matcher
The id in a nested attributes hash was resolved against the parent's
association, and on a miss fell back to a collection-wide lookup that
also dropped default scopes. The matched document was then mass
assigned and pushed into the caller's association, so a user could
overwrite a document belonging to someone else by sending that
document's id in a nested form field.

MONGOID-5930 added allow_reparenting_via_nested_attributes to close
this, but the branch was reached unconditionally for HABTM
associations, so the flag never protected them.

- Drop the unconditional HABTM test, so HABTM honours the same flag as
  has-many and raises DocumentNotFound instead.
- Reject non-scalar ids in the nested builder, so an operator hash
  cannot survive conversion into an id query, and a malformed object id
  hash raises a Mongoid error rather than an unhandled BSON error.
- Stop setting allow_reparenting_via_nested_attributes back to true in
  load_defaults('9.0'). On the 9.0-stable branch this is done by
  defaulting the option to false (it previously defaulted to true).

A destroy of a document that is not in the association is now ignored
for every association type, rather than only when the flag is on.
Backport 5993 to 9.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.
The allowlist added in MONGOID-5939 inspected only the top-level keys of a
criterion, and only #where consulted it. The other entry points into the
selector -- and, or, nor, not, any_of, none_of, elem_match -- reach it
through Mergeable and bypassed the guard entirely, so `or('$where' => js)`
still ran with strict mode on. Nested forms such as
{'$expr' => {'$function' => ...}} and {'$or' => [{'$where' => ...}]} were
likewise invisible to a top-level check.

Move the guard to _mongoid_expand_keys, the one point every user-supplied
expression passes through, and add a second rule: $where, $function, and
$accumulator are rejected at any depth. The top-level allowlist is
unchanged; recursing with it would reject $gt, $in, and every aggregation
operator legitimately used inside $expr.

Flip allow_unsafe_query_operators to default to false. It is deliberately
not wired into load_defaults, so an application on older defaults still
gets the guard and has to opt out explicitly.

Criteria#for_js is unaffected when called directly, since js_query does not
go through this funnel, but it now raises when merged into another criteria
via or/any_of, which re-expand the merged selector. The string form of
#where still compiles to $where; that path is MONGOID-5993.
@jamis
jamis requested a review from Jibola September 17, 2026 19:10
@jamis
jamis requested a review from a team as a code owner September 17, 2026 19:10
Copilot AI lite review requested due to automatic review settings September 17, 2026 19:10
@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 encryption issues and moderate enforcement gaps must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Release candidate 9.0.12 hardening Mongoid’s in-memory queries, encryption handling, nested attributes, and unsafe query protection.

Changes:

  • Adds regexp execution limits and safe in-memory field access.
  • Improves encryption schema generation and fail-closed persistence checks.
  • Restricts unsafe query operators and disables nested-attribute reparenting by default.
  • Updates release metadata and related tests.

Review findings:

  1. lib/mongoid/encryptable.rb:55 — Critical, 1 vote: Memoized negative results can omit lazily loaded encrypted schemas and allow plaintext writes.
  2. lib/mongoid/encryptable.rb:79 — Moderate, 1 vote: The guard can call embeds_encrypted? on an incompatible target and raise NoMethodError.
  3. lib/mongoid/matcher/regexp_budget.rb:296 — Moderate, 3 votes: The final regexp match can exhaust the budget without raising a timeout.
  4. lib/mongoid/persistence_context.rb:230 — Critical, 1 vote: Bypassed automatic encryption can pass namespace validation and write plaintext.
  5. lib/mongoid/search_indexable.rb:102 — Moderate, 3 votes: Search-index polling can run outside the management scope and incorrectly raise NoEncryptionSchema.
File summaries
File Description
spec/support/crypt/models.rb Adds encryption test models.
spec/mongoid/tasks/database_spec.rb Tests collection creation for encrypted models.
spec/mongoid/matcher/regexp_budget_spec.rb Tests regexp budget behavior.
spec/mongoid/encryptable_spec.rb Tests encryption schema requirements.
spec/mongoid/criteria/queryable/selectable_where_spec.rb Tests query operator guarding.
spec/mongoid/criteria/queryable/selectable_logical_spec.rb Updates unsafe string query tests.
spec/mongoid/criteria_spec.rb Updates unsafe string criteria tests.
spec/mongoid/contextual/memory_spec.rb Tests safe in-memory field reads.
spec/mongoid/contextual/aggregable/memory_spec.rb Tests safe in-memory aggregation.
spec/mongoid/config/encryption_spec.rb Tests generated encryption schemas.
spec/mongoid/config/defaults_spec.rb Tests updated defaults.
spec/mongoid/attributes/nested_spec.rb Tests nested attribute ID validation.
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 removal behavior.
spec/integration/query_operator_guard_spec.rb Tests query guard integration.
spec/integration/matcher_regexp_timeout_spec.rb Tests regexp timeout integration.
spec/integration/matcher_operator_data/regex.yml Adds regex array matching cases.
spec/integration/encryption_spec.rb Tests encryption failure modes.
spec/integration/dots_and_dollars_spec.rb Updates unsafe operator expectations.
spec/integration/associations/has_and_belongs_to_many_spec.rb Tests reparenting configuration.
spec/integration/app_spec.rb Pins JSON for Rails compatibility.
product.yml Updates release metadata.
lib/mongoid/version.rb Sets version 9.0.12.
lib/mongoid/threaded.rb Adds collection-management thread state.
lib/mongoid/tasks/database.rb Applies collection-management scopes.
lib/mongoid/search_indexable.rb Scopes search-index operations.
lib/mongoid/persistence_context.rb Validates encryption schemas.
lib/mongoid/matcher/regexp_budget.rb Implements regexp execution budgets.
lib/mongoid/matcher/regex.rb Routes regex matching through the budget.
lib/mongoid/matcher/eq_impl_with_regexp.rb Uses budgeted regex equality matching.
lib/mongoid/matcher.rb Loads regexp budget support.
lib/mongoid/matchable.rb Budgets document matching.
lib/mongoid/indexable.rb Scopes index management.
lib/mongoid/field_readable.rb Safely reads field values.
lib/mongoid/errors/no_encryption_schema.rb Adds an encryption schema error.
lib/mongoid/errors/in_memory_regexp_timeout.rb Adds a regexp timeout error.
lib/mongoid/errors.rb Registers new errors.
lib/mongoid/encryptable.rb Detects encrypted embedded schemas.
lib/mongoid/criteria/queryable/selectable.rb Validates query operators.
lib/mongoid/criteria/queryable/mergeable.rb Applies validation during criteria expansion.
lib/mongoid/contextual/memory.rb Budgets memory queries and uses safe reads.
lib/mongoid/contextual/aggregable/memory.rb Uses safe reads during aggregation.
lib/mongoid/config/encryption.rb Generates recursive encryption schema maps.
lib/mongoid/config.rb Adds configuration options and changes defaults.
lib/mongoid/collection_configurable.rb Exempts collection management from encryption checks.
lib/mongoid/association/relatable.rb Adds tolerant relation resolution.
lib/mongoid/association/referenced/has_many/proxy.rb Bounds regexp-based association removal.
lib/mongoid/association/nested/nested_buildable.rb Rejects non-scalar nested IDs.
lib/mongoid/association/nested/many.rb Resolves nested IDs within associations.
lib/mongoid/association/depending.rb Renames dependency ownership metadata.
lib/config/locales/en.yml Adds new error messages and guidance.
Review details

Suppressed comments (1)

lib/mongoid/encryptable.rb:82

  • The plain-target guard only checks for encrypted?, then unconditionally calls klass.embeds_encrypted?. A valid non-Mongoid embedded target that happens to define an unrelated encrypted? method will therefore make schema generation fail with NoMethodError; require the recursive method as well (or check for the Mongoid encryption contract) before descending.
          next false unless klass.respond_to?(:encrypted?)
          next false if path.include?(klass)

          klass.encrypted? || klass.embeds_encrypted?(path + [ klass ])
  • Files reviewed: 51/51 changed files
  • Comments generated: 4
  • Review effort level: Lite

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

Comment on lines +55 to +57
return @requires_encryption_schema if defined?(@requires_encryption_schema)

@requires_encryption_schema = encrypted? || embeds_encrypted?([ self ])
Comment on lines +230 to +232
schema_map = client.options.dig(:auto_encryption_options, :schema_map)
namespace = "#{client.database.name}.#{collection_name}"
return if schema_map&.key?(namespace)
# engine that can reach this rescue at all has them.
raise timeout_error(budget, pattern&.timeout || ::Regexp.timeout || budget.limit)
ensure
budget.charge(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started)
Comment on lines +102 to +104
Threaded.with_collection_management do
collection.search_indexes(options)
end
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 5373073 into mongodb:9.0-stable Sep 17, 2026
79 checks passed
@jamis
jamis deleted the updates-9.0 branch September 17, 2026 22:17
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