> 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.2.md).

# v2.4.2

Released: August 11, 2026

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

**v2.4.2** is a **hardening** release. It enforces RFC 7518 minimum key sizes for HMAC `jwt.key` ([#1017](https://github.com/prest/prest/pull/1017)), withholds credential headers from custom query templates and makes rejected interpolated values fail the request instead of silently emptying ([#1023](https://github.com/prest/prest/pull/1023)), stops writing caller-influenced SQL to logs, and picks up dependency updates ([#1015](https://github.com/prest/prest/pull/1015), [#1005](https://github.com/prest/prest/pull/1005)).

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

{% hint style="danger" %}
**Check your `jwt.key` length before upgrading.** An HMAC key shorter than the minimum for its algorithm (32 bytes for HS256) is discarded at startup. pREST **does not refuse to start** — it disables `/auth` and the JWT middleware and keeps serving, so a short key turns an authenticated deployment into an **unauthenticated** one. See [Upgrading from v2.4.1](#upgrading-from-v241).
{% endhint %}

This release includes the MCP `[expose]` enforcement from [v2.4.1](/releases/v2.4.1.md), and relaxes the over-broad script-value screen that release introduced.

***

## Highlights

### Minimum HMAC key sizes for `jwt.key` ([#1017](https://github.com/prest/prest/pull/1017))

pREST moved from the unmaintained `square/go-jose.v2` to `go-jose/go-jose/v4`, which enforces RFC 7518 key sizes and returns an error for undersized HMAC keys. Rather than surface that at request time, pREST validates the key at config load:

| `jwt.algo`                                          | Minimum `jwt.key` length                         |
| --------------------------------------------------- | ------------------------------------------------ |
| `HS256` (also the default when `jwt.algo` is unset) | **32 bytes**                                     |
| `HS384`                                             | **48 bytes**                                     |
| `HS512`                                             | **64 bytes**                                     |
| `RS*`, `ES*`, `PS*`, `EdDSA`                        | Not checked — `jwt.key` is not used as a MAC key |

The length is the byte length of the raw string, so a 32-character ASCII secret satisfies HS256.

A key below the minimum is **discarded**, and the features that depend on it disable themselves. Startup logs both events at `ERROR`:

```
level=ERROR msg="jwt.key too short for HMAC algorithm" algo=HS256 got=6 want=32
level=ERROR msg="auth disabled: jwt.key is empty"
```

A configured `jwt.jwks` or `jwt.wellknownurl` is unaffected — JWT verification continues against the JWKS even if the HMAC key is discarded.

See [Auth — HMAC key requirements](/api-reference/auth.md#hmac-key-requirements-v242).

### `jwt.algo` is now enforced ([#1017](https://github.com/prest/prest/pull/1017))

`jwt.algo` was accepted but discarded in earlier v2 releases: tokens were parsed without restricting the permitted signature algorithm. It is now passed to the parser as the single allowed algorithm, which structurally prevents algorithm-confusion attacks.

Two consequences for existing deployments:

* **A token whose `alg` header does not match `jwt.algo` is now rejected** with `401` and `{"error": "failed JWT token parser"}`.
* **The value is matched case-sensitively against a fixed set** — `EdDSA`, `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`. Anything else, including `hs256` in lowercase or an explicitly empty `algo = ""`, makes every request return **HTTP 500** with `unsupported JWT signature algorithm`. Leave `jwt.algo` unset to get the `HS256` default.

### Bind values in custom query scripts ([#1023](https://github.com/prest/prest/pull/1023))

Values a `/_QUERIES` template interpolates become part of the SQL text, so pREST screens them. This release makes the screen predictable and gives templates a way out of it entirely.

**Bind free-form values** with `sqlVal`, `sqlList`, or `ident` and the screen does not apply — the value travels to PostgreSQL out of band, where it can never be parsed as SQL:

```sql
-- interpolated: screened, and rejected for values like 'compra do mes'
SELECT * FROM articles WHERE slug = '{{.slug}}'

-- bound: the caller's value arrives verbatim, whatever it contains
SELECT * FROM articles WHERE slug = {{sqlVal "slug"}}
```

| Helper              | Use for                                       | Renders            |
| ------------------- | --------------------------------------------- | ------------------ |
| `{{sqlVal "key"}}`  | A single value                                | `$1`               |
| `{{sqlList "key"}}` | A repeated query parameter (`?tag=a&tag=b`)   | `($1,$2)`          |
| `{{ident "key"}}`   | A table or column name, which cannot be bound | `"public"."users"` |

Three related changes:

* **The keyword screen now runs only on values containing a space.** This fixes [#1030](https://github.com/prest/prest/issues/1030), where [v2.4.1](/releases/v2.4.1.md) blanked single-word values such as the slug `sao-joao-do-sul` (the token `do` is a SQL keyword). The character allow-list and the `--` / `::` rejection still apply to every value.
* **A rejected value that is interpolated now fails the request** with `400`, instead of substituting an empty string and returning `200` with the wrong rows:

  ```
  invalid value for parameter slug: it contains SQL syntax that cannot be
  interpolated safely; use the sqlVal template helper to bind free-form values
  ```

  The offending value is never echoed back. `sqlVal` and `sqlList` are exempt, since a bound value is never rendered into SQL text.
* **Query parameters named `header`, `_param`, or `_header` are ignored** — they are reserved for template data.

See [Custom Queries — Binding values](/api-reference/custom-queries.md#binding-values-sqlval-sqllist-ident-v242).

### Credential headers withheld from templates ([#1023](https://github.com/prest/prest/pull/1023))

A bearer token is plain base64url text, so it passed the value screen untouched and a template referencing it would interpolate the caller's credential straight into SQL — which was then logged.

These headers are now blanked before templates see them, in both the interpolated and the bound form:

`Authorization` · `Proxy-Authorization` · `Cookie` · `X-Api-Key` · `X-Auth-Token` · `X-Access-Token`

The request still succeeds; the value is simply empty. A template that scoped rows by the caller's token will now match nothing — move that logic to [permissions](/get-started/permissions.md) or pass a non-credential header.

Other headers rejected by the screen are blanked and logged at `WARN` with the header name only, rather than failing the request — an ordinary `User-Agent` fails the character allow-list on `(` and `;`, so erroring would reject nearly every browser request.

### SQL no longer written to logs ([#1023](https://github.com/prest/prest/pull/1023))

Statements produced from custom query templates are caller-influenced, so they are no longer logged on either the read or the write path. CRUD statements are still logged at `debug`, but their parameter **values** are replaced by a count:

```
level=DEBUG msg="generated SQL" parameter_count=2
```

Custom query script SQL is not visible in logs at any level. Use the database's own statement logging when you need it.

### Script path traversal rejected ([#1023](https://github.com/prest/prest/pull/1023))

Script resolution now verifies that the resolved `.sql` file is inside the queries directory, both lexically and after resolving symlinks. `..` segments and symlinks pointing outside the tree return **400** `invalid script path: <folder>/<script>`. This backs up the identifier validation already applied to the HTTP path, covering callers that reach the adapter directly.

### Dependency updates

`lestrrat-go/jwx/v3` 3.1.1 → 3.2.0 ([#1015](https://github.com/prest/prest/pull/1015)) and `google.golang.org/grpc` 1.81.1 → 1.82.1 ([#1005](https://github.com/prest/prest/pull/1005)). Both are dependency-only with no source changes; JWKS handling and OpenTelemetry export behave as before.

***

## Changes since v2.4.1

| PR                                                | Summary                                                                                                                                                                                                                                                       |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [#1017](https://github.com/prest/prest/pull/1017) | RFC 7518 minimum HMAC key sizes for `jwt.key`; `go-jose/v4` migration; `jwt.algo` enforced as the sole permitted signature algorithm                                                                                                                          |
| [#1023](https://github.com/prest/prest/pull/1023) | Credential headers withheld from script templates; rejected interpolated values fail with `400`; script SQL removed from logs; script path traversal rejected; [#1030](https://github.com/prest/prest/issues/1030) screen relaxation; CI workflow permissions |
| [#1015](https://github.com/prest/prest/pull/1015) | Bump `github.com/lestrrat-go/jwx/v3` 3.1.1 → 3.2.0                                                                                                                                                                                                            |
| [#1005](https://github.com/prest/prest/pull/1005) | Bump `google.golang.org/grpc` 1.81.1 → 1.82.1                                                                                                                                                                                                                 |

Full detail: [compare v2.4.1...v2.4.2](https://github.com/prest/prest/compare/v2.4.1...v2.4.2). Coming from v2.4.0, see also [Changes since v2.4.0](/releases/main-since-v2.4.0.md).

***

## Upgrading from v2.4.1

1. **Measure `jwt.key` first.** `printf '%s' "$PREST_JWT_KEY" | wc -c` must be at least 32 for HS256, 48 for HS384, or 64 for HS512. Rotate the secret **before** deploying — a short key does not stop startup, it disables `/auth` (which then returns `404`) and passes requests through unauthenticated. After deploying, grep the startup logs for `jwt.key too short`.
2. **Leave `jwt.algo` unset unless you mean it.** A value outside the supported set — including wrong case — makes every request return `500`. If you set it, existing tokens must be signed with that exact algorithm or they now fail with `401`.
3. **Audit `/_QUERIES` templates for credential headers.** `{{index .header "Authorization"}}` and `{{sqlVal "header.Authorization"}}` now render empty.
4. **Rewrite interpolated free-form values as bound values** — `WHERE slug = '{{.slug}}'` becomes `WHERE slug = {{sqlVal "slug"}}`. Search phrases are the common case: a phrase containing `do`, `as`, or `or` is exactly what the screen refuses, and it now returns `400` rather than the wrong rows.
5. **Expect script SQL to disappear from logs.** If a runbook relied on reading generated statements from `prestd` output, switch to PostgreSQL statement logging.
6. Deploy `prest/prest:v2.4.2`, the matching binary, or `go install …@v2.4.2`.

Coming from v2.4.0, apply the [v2.4.1 upgrade notes](/releases/v2.4.1.md#upgrading-from-v240) as well — MCP discovery changes when `[expose]` is active.

***

## Related

* [Auth — HMAC key requirements](/api-reference/auth.md#hmac-key-requirements-v242)
* [Custom Queries — Binding values](/api-reference/custom-queries.md#binding-values-sqlval-sqllist-ident-v242)
* [Configuring pREST — JWT](/get-started/configuring-prest.md#jwt)
* [v2.4.1 release notes](/releases/v2.4.1.md) · [v2.4.0 release notes](/releases/v2.4.0.md)
* [Acronyms](/readme/acronyms.md) · [JWT](/readme/acronyms.md#jwt) · [SQL](/readme/acronyms.md#sql) · [MCP](/readme/acronyms.md#mcp)
