# Source filters should not leak sibling fields

Elasticsearch clients often use `_source` filtering to keep response payloads small or avoid returning fields a particular view does not need. The gateway accepted array-shaped top-level filters, but a string such as `"profile.name"` silently returned the complete source. Nested paths were compared only with top-level keys, so even some accepted filters returned the wrong shape. A caller asking for a profile name could therefore receive sibling contact fields or unrelated metadata.

A live probe against native Elasticsearch 9.5.5 established four narrow response contracts. A single dotted field returned `{"profile":{"name":"Ada"}}`; `profile.contact.*` preserved the nested contact object; an include of `profile.*` plus an exclusion of `profile.contact.phone` retained the email but removed the phone; and a numeric `_source` value failed with HTTP 400. The changed gateway produced the same shapes against pinned Qdrant 1.15.5. The sanitized [endpoint evidence](https://github.com/dstockton/qdrant-es-gateway/blob/main/validation/evidence/elasticsearch-9.5.5-source-filtering-2026-10-11.json) records the fixture, requests, statuses, and projected sources.

The gateway now parses source filters before index lookup or Qdrant access, supports string and string-array patterns plus the singular and plural include/exclude options, and reconstructs nested objects while applying `*` and `?` wildcards. Collapse inner hits use the same parser. Malformed or mixed-type filters fail rather than falling back to the full source.

This is response minimization, not field-level authorization. The gateway still hydrates the complete source before applying the filter, so source filtering does not reduce Qdrant read bandwidth or in-process memory use. The probe covers nested objects and common wildcard forms, not every Elasticsearch source-filtering edge case. OpenSearch was not run, so this result does not claim OpenSearch parity.
