> ## Documentation Index
> Fetch the complete documentation index at: https://docs.37audits.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Auditor

> Checks that the site search page caps page size, returns nothing for an empty query, and does not nest redirect parameters in pagination links

<Info>
  **Security Auditor** — checks that the site search page caps its page size, returns nothing for an empty query, and does not nest `redirect` parameters in pagination links.
</Info>

<CardGroup cols={2}>
  <Card title="Resilience" icon="shield">
    An uncapped results page size lets anyone force very large search queries against the index.
  </Card>

  <Card title="Content exposure" icon="eye">
    An empty query that lists the whole index exposes content that was never meant to be browsed.
  </Card>
</CardGroup>

This auditor targets the **search page** of the site (for example `https://example.com/search`), either the monitored URL itself or the page named by the `searchPage` preference. It fetches that page once, detects the platform, and runs the rules of the matching platform visitor. The first supported platform is **Liferay**; other platforms (for example WordPress) can be added as new visitors with their own rules and codes.

## Preferences

These settings can be configured per monitored URL from the URL's auditor configuration tab. Only `resultSelector` is required: the platform does not run the auditor for a URL until it is set (the Overview shows *Needs configuration*), so the `405` failure below only appears for runs started outside the platform.

| Preference | Type | Default | Meaning |
| - | - | - | - |
| `resultSelector` | string | — (required) | CSS selector or XPath (starts with `/` or `(`) that matches **one search result item**, for example `li.list-group-item` or `//li[contains(@class,'result')]`. The auditor counts the matches on every probe page. |
| `queryParameter` | string | `q` | Name of the search term parameter. |
| `deltaParameter` | string | empty | Name of the page size parameter. When empty the Liferay visitor sends `delta` plus the Search Results portlet namespaced variant it discovers on the page (`_com_liferay_..._SearchResultsPortlet_..._delta`). |
| `searchPage` | string | empty | Path or absolute URL of the page that hosts the search portlet, resolved against the monitored URL (`/search`, `busca`, `https://search.example.com/`). Empty means the monitored URL is the search page. |
| `timeoutSeconds` | integer | `20` | Per request timeout, 1 to 30. A probe that does not answer in time is a `FAIL`. |
| `maxDelta` | integer | `100` | Largest page size the search page should honour. The large-delta probes request `maxDelta + 1` results (101 by default); a page returning more than `maxDelta` is a `FAIL`. |

## How it works

1. **Fetch** — loads the search page with the 37 Audits user agent (Basic auth is honoured). If the page cannot be fetched, or answers HTTP 400 or higher, the audit is `ERROR` (`500`).
2. **Detect** — asks each visitor whether it recognises the platform. The Liferay visitor matches when the monitored stack lists Liferay, the response carries a `Liferay-Portal` header, or the HTML contains the Search Results portlet or `Liferay.ThemeDisplay`. No match is `ERROR` (`501`).
3. **Validate** — the `resultSelector` preference must be set (`405`) and must parse as a CSS selector or XPath (`502`). Nothing else runs until it does.
4. **Probe (Liferay)** — sends four requests to the search page, each bounded by `timeoutSeconds`:
   * `?q=` — empty query, default page size.
   * `?q=*` — wildcard query, default page size. Also discovers the Search Results portlet namespace and a pagination link.
   * `?q=&delta=101` — empty query with a page size one above `maxDelta` (plus the namespaced delta when known).
   * `?q=*&delta=101` — wildcard query with a page size one above `maxDelta`.
5. **Count** — on every answered probe, the elements matching `resultSelector` are counted. A probe that times out is a `FAIL` on its own and is not counted.
6. **Redirect chain** — links on the wildcard page and on its first pagination page are decoded looking for a `redirect` parameter that nests another one.
7. **Aggregate** — `ERROR` wins, then `FAIL`, then `WARNING`; if every rule passes, the audit is `SUCCESS` (`200`).

## What it audits

Every row maps to one `checkRule*` method and one `CheckCode`. Codes `500`–`502`, `405` and `200` apply to every platform; `300` and `400`–`404` belong to the Liferay visitor. A future platform visitor continues the same sequence. CheckCode suffixes use status ranges: `200` success, `300–399` warning, `400–499` fail, `500–599` error.

| CheckCode | Status | When it fires | Recommendation | Why it matters |
| - | - | - | - | - |
| <a id="37A-SearchAuditor-500" />`37A-SearchAuditor-500` | `ERROR` | The search page or one of the probe requests could not be fetched, or answered with HTTP 400 or higher (for example a WAF returning 403). | Make sure `{probeUrl}` is reachable by the auditor and returns HTML. | — |
| <a id="37A-SearchAuditor-501" />`37A-SearchAuditor-501` | `ERROR` | No visitor recognised the platform (no Liferay in the stack, no `Liferay-Portal` header, no Liferay markup). | Point this auditor at the site search page of a supported platform (`{supported}`; for Liferay usually `/search`) or add the platform to the monitored stack. | — |
| <a id="37A-SearchAuditor-405" />`37A-SearchAuditor-405` | `FAIL` | The `resultSelector` preference is not configured, so results cannot be counted. | Set the `resultSelector` preference for `{url}` to the CSS selector or XPath that matches one search result item (for example `li.list-group-item`). | Until results can be counted, none of the search hardening rules can run, so the search page stays unaudited and its problems go unreported. |
| <a id="37A-SearchAuditor-502" />`37A-SearchAuditor-502` | `ERROR` | The `resultSelector` preference is set but does not parse as a CSS selector or XPath. | Fix the `resultSelector` preference `{selector}` so it parses as a CSS selector or an XPath expression. | — |
| <a id="37A-SearchAuditor-404" />`37A-SearchAuditor-404` | `FAIL` | Liferay: a probe (`?q=`, `?q=*`, `?q=&delta={maxDelta+1}` or `?q=*&delta={maxDelta+1}`) did not answer within `timeoutSeconds`. One check per probe that timed out. | Make `{probeUrl}` answer within `{timeoutSeconds}` seconds: cap the pagination delta, disable empty searches and index only what needs to be searchable. | A search that does not answer in time is one visitors give up on, and slow queries triggered by bots can tie up the server for everyone else. |
| <a id="37A-SearchAuditor-401" />`37A-SearchAuditor-401` | `FAIL` | Liferay: `?q=` (empty query) returned one or more results. | Disable "Allow Empty Searches" on the Liferay Search Bar / Search Results portlet so `{probeUrl}` returns no results. | An empty query that returns everything exposes the entire index to scraping and turns every accidental or automated empty search into an expensive query for the server. |
| <a id="37A-SearchAuditor-300" />`37A-SearchAuditor-300` | `WARNING` | Liferay: `?q=*` (wildcard query) returned one or more results, so the whole index can be browsed. | Reject or sanitise wildcard-only queries on the Liferay Search Bar so `{probeUrl}` returns no results. | A wildcard query lets anyone list and scrape the whole search index, including content that was never meant to be found by browsing. |
| <a id="37A-SearchAuditor-403" />`37A-SearchAuditor-403` | `FAIL` | Liferay: `?q=&delta={maxDelta+1}` (empty query, page size above the maximum) returned one or more results. | Disable "Allow Empty Searches" and cap the pagination delta on the Liferay Search Results portlet so `{probeUrl}` returns no results. | An empty query combined with a huge page size is the cheapest way for a bot to dump the whole index and the costliest query the search backend can receive. |
| <a id="37A-SearchAuditor-400" />`37A-SearchAuditor-400` | `FAIL` | Liferay: `?q=*&delta={maxDelta+1}` returned more than `maxDelta` results in one page. | Cap the Search Results portlet pagination delta at `{maxDelta}` or less; `{probeUrl}` currently returns `{count}` results in one page. | An unbounded page size lets a single request pull thousands of results, which can overload the search backend and lets scrapers download the whole index in a few calls. |
| <a id="37A-SearchAuditor-402" />`37A-SearchAuditor-402` | `FAIL` | Liferay: a link on the results page or on the first pagination page carries a `redirect` parameter that nests another `redirect` parameter (depth 2 or more). | Remove the redirect parameter from the Liferay Search Results / Asset Publisher pagination URLs so `{pageUrl}` links stop nesting the previous URL. | URLs that grow on every page change eventually exceed server limits and fail, and the bloated links hurt sharing, caching and search engine crawling. |
| <a id="37A-SearchAuditor-200" />`37A-SearchAuditor-200` | `SUCCESS` | Every platform rule passed. Message is `Search page "{url}" passed all {platform} search rules.` | — | — |

## Output documentation

| Status | Description | Test logic |
| - | - | - |
| **SUCCESS** | The search page passed every rule of the detected platform | Liferay: every probe answered in time, `q=` and `q=&delta={maxDelta+1}` return 0 results, `q=*` returns 0 results, `q=*&delta={maxDelta+1}` returns at most `maxDelta` results, and no link nests a `redirect` inside a `redirect` (`200`). |
| **WARNING** | The search page can be browsed with a wildcard | Liferay: `q=*` returns results (`300`) and nothing else fails. |
| **FAIL** | The search page is misconfigured or cannot be audited as configured | Missing `resultSelector` (`405`); probe timeout (`404`); results for an empty query (`401`); results for an empty query with a large delta (`403`); more than `maxDelta` results for `delta={maxDelta+1}` (`400`); nested `redirect` parameters (`402`). Several can fire together. |
| **ERROR** | The auditor could not run the rules | Search page or probe request failed or returned HTTP 400+ (`500`); platform not recognised (`501`); `resultSelector` does not parse (`502`). |

## Risks and considerations

### Uncapped page size is a denial-of-service vector

* Liferay reads the `delta` parameter to size the results page. Without a cap, `?delta=5000` makes the portal ask Elasticsearch for thousands of documents and render them all, on every request.
* Liferay caps `delta` at 200 by default, but custom themes, fragments and portlet configuration can raise or bypass it. This auditor expects `maxDelta` (100 by default) or less, and only ever asks for one result more than that.

### Empty searches list the whole index

* With "Allow Empty Searches" enabled, `?q=` returns every indexed document. That surfaces content that is only reachable by search (drafts, internal pages, documents) and makes scraping trivial.

### Redirect chains break URLs

* Liferay portlets append the current URL as a `redirect` parameter so "back" navigation works. When a pagination link re-encodes a URL that already carries a `redirect`, every page change nests one more level. After a few clicks the URL is several kilobytes long, breaks caches and proxies, and eventually hits request-line limits.
* Links from other portlets on the search page (for example Asset Publisher) are scanned too, because the same mechanism affects them.

### Timeouts are findings

* Liferay renders every result item server side. A request that cannot answer within `timeoutSeconds` (default 20) is a sign that the page size or the empty search is not capped, and is reported as a `FAIL` rather than an auditor error.
* Slow but correctly configured sites can raise `timeoutSeconds` up to 30 per URL.

### What this auditor does not check

* It does not audit search relevance, indexing coverage, or the search bar on other pages.
* If the search index has `maxDelta` or fewer documents matching `*`, the `delta` rule cannot prove a violation and passes.
* If the results page has no pagination link, the redirect chain rule only inspects the results page itself.
* Counting depends entirely on `resultSelector`. A selector that matches nothing makes every count rule pass; verify it against the live page when configuring the URL.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.