> For the complete documentation index, see [llms.txt](https://docs.prestd.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.prestd.com/releases/v2.4.1.md).

# v2.4.1

Released: July 28, 2026

[GitHub tag](https://github.com/prest/prest/releases/tag/v2.4.1) · [Compare v2.4.0...v2.4.1](https://github.com/prest/prest/compare/v2.4.0...v2.4.1)

**v2.4.1** is a **security** release with a single commit ([#1016](https://github.com/prest/prest/pull/1016)). It makes the MCP endpoint honour the `[expose]` settings — previously `/_mcp` could enumerate the full catalog even when the REST listing routes were denied — and adds a SQL-keyword screen to values that custom query scripts interpolate into SQL.

* **Docker:** `prest/prest:v2.4.1`
* **Go install:** `go install github.com/prest/prest/v2/cmd/prestd@v2.4.1`

{% hint style="warning" %}
The script-value screen shipped here was too broad and was **relaxed in** [**v2.4.2**](/releases/v2.4.2.md) ([#1030](https://github.com/prest/prest/issues/1030)). On v2.4.1, a single-word value containing a SQL keyword — `sao-joao-do-sul` splits into a token `do` — is silently blanked, and the request still returns `200` with the wrong rows. **Go straight to** [**v2.4.2**](/releases/v2.4.2.md) rather than stopping on v2.4.1.
{% endhint %}

No configuration keys were added or renamed, and no defaults changed.

***

## Highlights

### MCP honours `[expose]` ([#1016](https://github.com/prest/prest/pull/1016))

`/_mcp` was never routed through the exposure middleware, so an operator who disabled the `/databases`, `/schemas`, or `/tables` REST routes was still serving the entire catalog to any MCP client. The MCP handler now checks the same settings before answering.

When a listing is denied, the corresponding tool returns **HTTP 400** with a JSON-RPC error:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":400,"message":"unauthorized listing"}}
```

The REST routes keep returning **401** with `{"error": "unauthorized listing"}` — same message, different status, because the two surfaces report errors differently.

Discovery (`GET /_mcp`, `tools/list`) is pruned rather than blocked:

| Tool                                         | Behaviour when its listing is denied                 |
| -------------------------------------------- | ---------------------------------------------------- |
| `prest.list_databases`                       | Not advertised; `tools/call` returns `400`           |
| `prest.list_schemas`                         | Not advertised; `tools/call` returns `400`           |
| `prest.list_tables`                          | Not advertised; `tools/call` returns `400`           |
| `prest.select.{database}.{schema}.{table}`   | **Not enumerated at all** if *any* listing is denied |
| `prest.describe_table`, `prest.select_table` | Always advertised                                    |

The per-table tools are withheld because their names embed database, schema, and table names and their descriptions embed column names — enumerating them made `GET /_mcp` a full catalog dump with no `tools/call` required.

`[expose]` governs **discovery**; `[access]` governs **reads**. A client that already knows a table name can still read it through `prest.select_table` subject to permissions. See [MCP over HTTP — Tools reference](/get-started/mcp-over-http.md#tools-reference) and [Configuring pREST — Expose Data](/get-started/configuring-prest.md#expose-data).

### Clarified `[expose]` semantics

The three listing flags only take effect while `enabled = true`. The check is "allowed unless exposure control is on *and* this listing is off", so hiding a listing takes **both** keys:

```toml
[expose]
enabled = true      # required — without this, the flags below do nothing
databases = false   # hide the databases listing
schemas = true
tables = true
```

This also means a deployment on the default `enabled = false` is unaffected by this release.

### SQL-keyword screen on script values ([#1016](https://github.com/prest/prest/pull/1016))

Values that a `/_QUERIES` script template interpolates become part of the SQL text. The pre-existing character allow-list was not enough: `0 UNION SELECT passwd FROM pg_shadow` needs no quote, comma, or parenthesis, so it passed through into an unquoted comparison such as `WHERE 1 = {{.field1}}`.

Values are now also rejected when they contain `--`, `::`, or a SQL keyword token (58 keywords, matched case-insensitively, with leading digits stripped so `0union` is caught as `union`).

{% hint style="danger" %}
On v2.4.1 this screen runs on **every** value and on **every** inbound header, and a blanked value does not fail the request — the script runs with an empty string substituted and returns `200`. Legitimate values are affected: `?status=null`, `?sort=order`, `?type=table`, and any slug containing `do`, `as`, or `or`. [v2.4.2](/releases/v2.4.2.md) restricts the screen to values containing a space and fails the request instead of silently substituting.
{% endhint %}

***

## Changes since v2.4.0

| PR                                                | Summary                                                                                                                                                               |
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [#1016](https://github.com/prest/prest/pull/1016) | MCP endpoint honours `[expose]`; listing tools refused and per-table tools withheld when exposure control is active; SQL-keyword screen on custom query script values |

Full detail: [compare v2.4.0...v2.4.1](https://github.com/prest/prest/compare/v2.4.0...v2.4.1).

***

## Upgrading from v2.4.0

Upgrade to [v2.4.2](/releases/v2.4.2.md) instead — it contains this release plus the fix for the over-broad script screen. If you do deploy v2.4.1:

1. Deploy `prest/prest:v2.4.1`, the matching binary, or `go install …@v2.4.1`.
2. **On the default `[expose] enabled = false`, nothing changes.** MCP behaviour only shifts for deployments that actively restrict listings.
3. **If you restrict listings**, MCP clients relying on auto-discovered `prest.select.{database}.{schema}.{table}` tools must switch to the generic `prest.select_table` tool.
4. **Audit custom query scripts.** Any script parameter or header whose value contains a SQL keyword, `--`, or `::` is silently blanked. Rewrite templates to bind values instead of interpolating them — see [Custom Queries](/api-reference/custom-queries.md).

***

## Related

* [MCP over HTTP](/get-started/mcp-over-http.md)
* [Configuring pREST — Expose Data](/get-started/configuring-prest.md#expose-data)
* [Custom Queries](/api-reference/custom-queries.md)
* [v2.4.2 release notes](/releases/v2.4.2.md) · [v2.4.0 release notes](/releases/v2.4.0.md)
* [Acronyms](/readme/acronyms.md) · [MCP](/readme/acronyms.md#mcp) · [SQL](/readme/acronyms.md#sql)
