A search box is a query interpreter, an authorisation boundary and a data export tool rolled into one input field, and most applications secure it as if it were none of these. Site search, record lookup and typeahead all pass untrusted text to an engine whose index often holds more than the caller may see. This guide covers the risks specific to search features: query injection into search engines, data leakage through what gets indexed and how results, facets and suggestions are built, enumeration of users and records, and resource exhaustion. It is for application security engineers, search developers and architects. General attack classes are in the Web Application Attacks Guide.
Key takeaways
- The index is a copy of your data with its own access model. Anything indexed can be returned unless permissions travel with the documents and are enforced on every query, suggestion and aggregation.
- Never hand users a full query language. Map the search box to a match style query and build filters server-side; expose operators only through a constrained parser.
- Side channels leak as much as results. Result counts, facet totals, autocomplete terms, relevance scores and "no results" wording can reveal records the caller cannot open.
- Search is the fastest enumeration tool in your application. User directory search, email lookup and record ID search need the same generic responses, rate limits and authorisation as login.
- Service credentials behind search are high value. The account the application uses to query the engine usually reads every index, so scope it, keep it server-side and monitor it like any privileged non-human identity.
Why search features are a distinct risk
A normal application page shows one object after an authorisation check. A search feature runs one query across many objects and the authorisation check, if any, has to be built into the query itself. Three properties make it different:
- Denormalised copies: pipelines flatten records, attachments and comments into documents, so fields the interface never shows (internal notes, emails, API keys pasted into tickets) become searchable.
- Aggregate answers: counts, facets, highlights and suggestions may be computed outside the filter that trims the result list.
- Expressive input: engines accept Boolean operators, field targeting, wildcards and regular expressions; forwarded user text can change what the query asks.
The attacker goals map cleanly onto MITRE ATT&CK: harvesting content from internal search and knowledge bases is Data from Information Repositories (T1213), and using lookup features to list valid users is Account Discovery (T1087).
Threats to search features at a glance
| Threat | How it shows up in search | Primary control | Weakness or risk reference |
|---|---|---|---|
| Query injection | User text is concatenated into a search DSL, Lucene query string, SQL LIKE clause or NoSQL filter, letting the caller add fields, operators or remove the tenant filter | Structured query building; match queries for free text; server-side filters | OWASP Injection Prevention Cheat Sheet; NIST SP 800-53 SI-10 |
| Over-indexing | Sensitive fields or whole records indexed because the pipeline indexes everything by default | Field allow-list in the indexer; data classification before indexing | NIST CSF 2.0 PR.DS-01 |
| Missing document-level authorisation | Results include records from other users or tenants; direct document fetch by ID after search | Permission metadata on each document, enforced on every query | OWASP API1:2023 Broken Object Level Authorization; SP 800-53 AC-3 |
| Property-level exposure | Hidden fields returned in the result source, highlights or exports | Field-level security; explicit response field lists | OWASP API3:2023 Broken Object Property Level Authorization |
| Side-channel leakage | Facet counts, totals, autocomplete terms or scoring reveal hidden records | Compute aggregations and suggestions inside the permission filter; per-user suggestion sources | CWE-203 Observable Discrepancy |
| Enumeration | User or email lookup returns different messages, status codes or timings for existing and missing entries | Generic responses, consistent timing, rate limits, authentication before directory search | CWE-204 Observable Response Discrepancy |
| Query string leakage | Search terms containing personal data or secrets stored in web logs, analytics, browser history and Referer headers | POST for sensitive search; log redaction; Referrer-Policy | CWE-598 Use of HTTP Request With Sensitive Query String |
| Resource exhaustion | Leading wildcards, regular expressions, deep pagination and huge page sizes | Disable expensive query types; cap size and offset; rate limit | OWASP API4:2023 Unrestricted Resource Consumption; SP 800-53 SC-5 |
Preventing query injection
The OWASP Injection Prevention Cheat Sheet describes injection as untrusted data sent to an interpreter, prefers a safe parameterised API, treats allow-list validation as useful but not a complete defence, and keeps interpreter-specific escaping for cases where no parameterised API exists. Search engines are interpreters, and the same order applies.
- Use a match style query for the search box: Elastic's query_string documentation does not recommend that query for search boxes because it errors on invalid syntax, pointing instead to match or simple_query_string. A match query treats input as text, not operators.
- Build filters in code, never from text: tenant, owner, status and date filters belong in a structured filter clause assembled from the authenticated session.
- Allow-list advanced options: accept only known sort and facet field names, mapped server-side.
- Never forward client-supplied query DSL: use stored templates with typed parameters; the front end is controlled by the attacker.
- Database-backed search: where search is a SQL LIKE or full-text predicate, bind the search term as a parameter and escape the wildcard characters
%and_if users should not control pattern matching. See the SQL Injection Prevention Guide. For document stores, filters built from JSON bodies are a classic operator injection path: see the NoSQL Injection Prevention Guide. - Encode results on output: search pages reflect the query back ("results for ...") and render highlighted snippets that contain stored content. Encode both as untrusted output. See the Cross-Site Scripting (XSS) Prevention Guide.
Controlling what gets indexed
The most reliable way to stop a search feature leaking data is not to put the data in the index.
- Classify sources before connecting them. List each source system, the classification of its data and who may see it. See the Data Classification Guide.
- Index an allow-list of fields, marking each searchable, retrievable or filter-only; dynamic mapping indexes everything sent.
- Scan content for secrets and personal data at ingest, redact matches and have exposed secrets rotated.
- Propagate deletions and permission changes, or the index becomes a shadow archive. Measure sync lag as a security metric.
- Separate indexes by tenant or sensitivity where volume allows, so a filter mistake returns nothing instead of another tenant's data. See the Multi-Tenant SaaS Authorisation and Tenant Isolation Guide.
Enforcing document and field permissions in the engine
There are two broad patterns. In application-side trimming, the application works out the caller's identity and groups and adds a filter to every query. In engine-side enforcement, the engine itself evaluates the caller's identity against permission metadata or role queries. The examples below show how three widely used engines document these controls. Platforms are listed alphabetically; this is not a ranking.
Azure AI Search
Microsoft documents security trimming as a filter pattern: a Collection(Edm.String) field such as group_ids, set filterable and not retrievable, holds the user or group identifiers per document, and each query carries a filter such as group_ids/any(g:search.in(g, 'group_id1, group_id2')). The documentation notes that the principal is only a string in a filter and that marking a field not retrievable is not a substitute for access control, so the application must establish group membership itself and apply the filter to every query. Microsoft also documents engine-side options (ACL and RBAC scopes for ADLS Gen2 and Blob content, SharePoint ACLs and Purview sensitivity labels) where the user's Microsoft Entra token is passed in the x-ms-query-source-authorization header. Permission changes reach results only after the index metadata is synchronised. Availability: as checked in November 2026, security filters are generally available and the other three approaches are in preview.
Elasticsearch
Elastic documents document level security as a query added to an entry in a role's indices array, and field level security as a field_security object with grant and except lists. Role queries can be templated with Mustache placeholders such as _user.username and _user.metadata, so one role can restrict each user to their own documents. Elastic notes that when a user holds several roles, document queries combine with OR and a role without a query removes the restriction. The limitations list states that aggregations can reveal field names and term counts from hidden documents and that relevance scoring ignores the role query, which matters for the side channels below. Elastic also says these restrictions are intended for read-only accounts.
OpenSearch
OpenSearch documents document-level security as a dls query in a role's index_permissions, with parameter substitution for ${user.name}, ${user.roles} and user attributes such as ${attr.jwt.<name>}. Queries from several DLS roles combine with OR, and the plugins.security.dfm_empty_overrides_all setting decides whether a role without DLS overrides another role's restrictions for the same user. The documentation states that DLS limits reads only, does not restrict write operations, and advises not to give DLS users write permissions.
Choosing a pattern: a single-tenant application with simple ownership rules can use an application-side filter, provided tests fail any query built without it. Strict multi-tenant isolation is best served by an index or alias per tenant with a tenant filter as defence in depth. Enterprise content with existing source ACLs suits engine-side permission metadata, accepting sync lag. Where analysts query the engine directly, use engine roles with document and field restrictions and review users holding several roles.
Closing side channels: counts, facets, suggestions and scores
Correctly filtered results are not enough if the extras around them are computed over the unfiltered set.
- Totals and facets: "312 results" and "HR (4)" are answers in their own right. Compute them inside the same permission filter as the hits, and test by searching as a low-privilege user for a term that only appears in restricted documents. The count must be zero.
- Autocomplete and suggestions: suggesters are usually built from a global term dictionary or a log of popular queries. Both leak: the first reveals terms from restricted documents, the second reveals what other users searched for. Build suggestions from per-tenant or permission-filtered sources, or switch them off for sensitive indexes. Elastic's documentation notes that suggesters are ignored under document level security, and Microsoft notes autocomplete and suggest are unavailable on Purview-enabled indexes: plan for this rather than routing suggestions through a separate unrestricted path.
- Highlights: restrict highlight fields to the returned field list.
Stopping enumeration through search
User directory search, "find a colleague", email lookup during sign-up and order or case number search all answer the question "does this exist?". The OWASP Authentication Cheat Sheet's guidance for login and recovery applies directly to these lookups: return a generic message whether or not the entry exists, keep HTTP status codes identical, avoid a quick exit when nothing matches so that response time is similar, and limit attempts at scale.
- Require authentication for directory search and scope results to the caller's organisation. Public people search should return only deliberately public profiles.
- Do not confirm identifiers to anonymous users. Send a message to the address instead of confirming it on screen.
- Recheck authorisation on every fetch after search and use non-guessable record IDs. See the Broken Access Control Prevention Guide.
- Set a minimum query length and small result pages, so a one-character query cannot dump the directory.
- Rate limit per account, per IP range and per tenant, with separate budgets for typeahead and full search. See the Rate Limiting and API Abuse Guide.
Protecting availability and query strings
- Expensive queries: Elastic's query_string documentation warns that leading wildcards force a check of every term in the index and that regular expressions such as
/.*n/can visit every term; it exposesallow_leading_wildcardand amax_determinized_stateslimit (default 10000). Turn off leading wildcards and regular expressions for user-facing queries and use the engine's setting for disallowing expensive queries where available. - Pagination, size and time: cap page size, maximum offset and per-query time; use cursor-based paging for legitimate deep access.
- Search terms in URLs: CWE-598 notes that query strings are saved in browser history, passed in Referer headers and stored in web logs. Use POST for search over sensitive data, set
Referrer-Policy: strict-origin-when-cross-originor stricter, and redact search parameters in web server logs and analytics.
Where identity fits
Every search feature has a non-human identity behind it: the service account, API key or managed identity the application uses to query the engine, and another used by the indexing pipeline to read source systems. A leaked query key that reads every index turns search into bulk export. Give query and indexing identities separate credentials, scope query credentials read-only to specific indexes, keep admin and write keys out of browsers and apps, prefer managed identities or federation over static keys, and rotate keys. See the Elasticsearch and OpenSearch Security Guide for engine hardening and the Permission-Aware RAG Guide where search results feed an AI assistant.
Implementation roadmap
The sequence below is an NHI Mgmt Group recommendation.
- Inventory search features, with engine, index, credential and data sources for each.
- Map index contents to classifications. For each index, list fields and classify them. Remove or redact fields that no search use case needs.
- Fix query construction. Replace string-built queries with structured builders and match style queries; move all filters server-side. Add unit tests that fail if a query is built without a tenant or permission filter.
- Add permission enforcement in audit mode. Run the permission filter in parallel with the current query in a pilot and log the difference in hit counts. A non-zero difference shows where users are currently seeing data they should not.
- Turn enforcement on in rings (internal users, pilot tenants, everyone) behind a feature flag; roll back only the toggle, never by widening credentials.
- Close side channels and enumeration. Move facets, counts and suggestions inside the filter, add generic responses and rate limits to lookups.
- Test continuously with two users, two tenants and restricted terms, after every new data source.
Decision guidance
Small teams with one application get most of the benefit from application-side filtering, a separate read-only query key, generic lookup responses and engine limits on expensive queries, and should keep restricted data out of the index until permissions are designed. Large or multi-tenant platforms should invest in index-per-tenant isolation, engine-side permission metadata synchronised from source systems and automated cross-tenant tests. For enterprise search over many sources, treat each connector as an integration with its own owner and permission sync, and prefer keeping highly sensitive sources out over relying on a preview feature.
Detection and measurement
Log each search request with the authenticated principal, tenant, index, filter applied (or a hash of it), result count and latency. Alert on queries without a permission filter, unusual indexes, very high result counts or offsets, bursts of short-prefix typeahead calls and engine access by credentials outside the application.
| Metric | What it shows | Target |
|---|---|---|
| Queries executed without a permission or tenant filter | Gaps in enforcement | Zero in production |
| Indexed fields without an assigned classification | Over-indexing risk | Zero for indexes holding personal or restricted data |
| Permission sync lag (source change to index update) | How long revoked users keep seeing documents | Agreed per data class, measured continuously |
| Cross-tenant and restricted-term test pass rate | Regression in authorisation | 100% on every release |
| Search credentials with write or admin rights used by applications | Over-privileged non-human identities | Zero |
Framework mapping
| Framework | Reference | Relevance to search |
|---|---|---|
| NIST SP 800-53 Rev 5 | AC-3 Access Enforcement; AC-6 Least Privilege | Document permissions on every query; scoped query credentials |
| NIST SP 800-53 Rev 5 | SI-10 Information Input Validation; SI-11 Error Handling | Structured query building; generic error and no-result responses |
| NIST CSF 2.0 | PR.AA-05; PR.DS-01; DE.CM-09 | Least privilege on indexes; protecting indexed data at rest; monitoring search use |
| CIS Controls v8.1 | Control 3 Data Protection; Control 16 Application Software Security | Data inventory and handling for indexes; secure search code |
| ISO/IEC 27001:2022 Annex A | 8.3 Information access restriction; 8.28 Secure coding | Permission trimming; injection-safe query construction |
| OWASP API Security Top 10 2023 | API1, API3, API4 | Object and property level authorisation; resource consumption |
| MITRE ATT&CK v20 | T1213, T1087 | Repository harvesting and account discovery through search |
Common failure patterns
- Filtering results but not facets: the list is empty but the sidebar shows "Legal (12)". Every aggregation needs the same filter as the hits.
- Forwarding front-end query JSON: the single-page app builds the engine query and the API passes it through, so anyone with browser tools can remove the tenant clause.
- Role combination widening access: a user given a second, unrestricted role for one task loses document restrictions everywhere, because in Elasticsearch a role without a document query removes the restriction for that index, and in OpenSearch the same happens when dfm_empty_overrides_all is enabled.
- Query keys in client code: a key embedded in a mobile app or JavaScript bundle has wider rights than the search page needs, turning the engine into an open data store.
- Directory search as an oracle: sign-up or invite lookups confirm which email addresses have accounts.
Practitioner checklist
- Replace string-built queries with structured builders; use match style queries for free text and never forward client query DSL.
- Index only allow-listed fields, scan content for secrets and personal data at ingest, and propagate deletions.
- Enforce document permissions on every query path, including counts, facets, highlights, suggestions and exports.
- Return generic, constant-time responses for user and record lookups, require authentication and set minimum query lengths.
- Disable leading wildcards and regular expressions for user queries; cap page size, offset and query time.
- Send sensitive searches by POST, set a strict Referrer-Policy and redact search terms in logs and analytics.
- Give query and indexing identities separate, read-only scoped credentials kept server-side, and rotate them.
- Run cross-tenant and restricted-term tests on every release and track permission sync lag.
Standards and references
- OWASP: Injection Prevention Cheat Sheet
- OWASP: Authentication Cheat Sheet
- MITRE: CWE-204 Observable Response Discrepancy
- MITRE: CWE-598 Use of HTTP Request With Sensitive Query String
- Microsoft Learn: Document-level access control in Azure AI Search
- Elastic: Controlling access at the document and field level
- Elastic: Query string query
- OpenSearch: Document-level security
Related NHI Mgmt Group resources: Web Application Attacks Guide · Broken Access Control Prevention Guide · Elasticsearch and OpenSearch Security Guide · Permission-Aware RAG Guide