Implement validate() for extensions in the extensions API

    • Type: Task
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: None
    • Component/s: None
    • Query Integration
    • 200
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Add a client-type-aware validate() entrypoint to the Extensions API so an extension can reject
      its own internal-only fields when supplied by an external client (field-level analog of the existing
      stage-level get_client_type).

      Background

      CVE-261 (SERVER-126247) hardened native
      $search/$searchMeta parsing to reject router-only internal fields (mongotQuery,
      mergingPipeline, metadataMergeProtocolVersion, requiresSearchSequenceToken,
      requiresSearchMetaCursor, limit, sortSpec, mongotDocsRequested, docsNeededBounds,
      view) when supplied by an external client. The check lives in
      LiteParsedSearchStage::validate() -> search_helpers::validateInternalSearchFieldsNotSetByUser()
      (error 5491300).

      In v9.0 the mongot extension provides $search/$searchMeta. On the extension path the stage
      is lite-parsed as DocumentSourceExtensionOptimizable::LiteParsedExpandable, whose validate()
      is the inherited no-op — so the native 5491300 rejection never runs.

      Current safety (verified for 9.0)

      Instrumented investigation confirmed the extension carries the user-supplied internal field
      verbatim into $extensionSearch as the opaque mongot query — it is _not split out into a
      mongos-side merging pipeline. mongot then rejects the malformed query
      (Query should contain either an operator ...) and planShardedSearch aborts. The user's
      mergingPipeline therefore never executes on mongos with router privileges, so the CVE-261
      mechanism does not reproduce on the extension path.

      This is safe but fragile: safety depends on the extension treating internal fields as inert query
      content rather than routing directives. There is no enforced trust boundary on the extension path.

      Desired robust fix

      Mirror the existing stage-level client-type mechanism at the field level, owned by the extension
      (no search-specific code in the host):

      • Stage-level today: AggStageDescriptor::get_client_type() (C-API api.h) ->
        AllowedWithClientType -> generic host enforcement (5491300). Fail-closed (defaults to
        internal).
      • Field-level needed: a generic Extensions-API entrypoint that receives the client type (internal vs
        external) so the extension can reject its own internal-only fields. The mongot extension owns the
        field list and the rejection; the host stays generic.

      Sketch

      1. C-API (src/mongo/db/extension/public/api.h): add a validate vtable fn on the parse node
        (or descriptor) taking a MongoExtensionClientType. Add at the END of the struct; bump the API
        minor version. Provide a fail-closed default.
      2. Host (document_source_extension_optimizable.cpp): in LiteParsedExpandable::validate(opCtx)
        compute isInternalClient(opCtx) and call the new generic _parseNode->validate(clientType).
        No hasExtensionSearchStage(), no search_helpers:: call — this also lets the existing
        search-specific TODO SERVER-121094 hooks be removed rather than multiplied.
      3. SDK: add the SDK virtual with a safe default (host_connector handle + adapters on both sides, per
        the API-change checklist in src/mongo/db/extension/CLAUDE.md).
      4. mongot extension (10gen/mongot-extension, Rust):
        implement the field-level rejection of its internal-only fields against its Rust SDK. Note that this will need to be a separate CLOUDP related ticket.

      Related / tracking

      Ticket Relevance
      SERVER-126247 Original CVE-261 fix (native path).
      SERVER-114364 "Support internal-only extension stages" (Closed/Fixed). Adds stage-level client-type metadata (get_client_type) so a whole extension stage can be rejected for external clients. Does not provide field-level validation, so it does not cover this need.
      SPM-4488 IDL-ify aggregation stages so internal-only field validation is auto-generated (long-term; postmortem's preferred end state). This field-level validate() entrypoint could be folded in here.
      SERVER-130885 "Validate $lookup internal-only fields are not set by external clients" — analogous native-side follow-up from the CVE-261 audit (SERVER-128089).

      No existing ticket tracks the field-level, client-type-aware extension validate() entrypoint —
      this is new work (sibling to SERVER-114364, or part of SPM-4488).

      Interim (already done)

      Test coverage is split by path:

      • jstests/with_mongot/e2e/search_internal_fields_rejected_by_external_client.js — native path;
        asserts 5491300. Excluded from the search-extension families.
      • jstests/with_mongot/e2e_search_extension/search_internal_fields_ignored_by_extension.js
        extension path; asserts the security invariant (injected internal fields never leak confidential
        data / never take effect), including the valid-operator + injected-mergingPipeline no-leak case.
        Runs only on the search-extension families.

            Assignee:
            Unassigned
            Reporter:
            Finley Lau
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: