For the complete documentation index, see llms.txt. This page is also available as Markdown.

v2.4.2

pREST v2.4.2 β€” HMAC jwt.key minimums, bound values for custom query scripts, credential headers withheld from templates, and SQL redacted from logs.

Released: August 11, 2026

GitHub tag Β· 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), withholds credential headers from custom query templates and makes rejected interpolated values fail the request instead of silently emptying (#1023), stops writing caller-influenced SQL to logs, and picks up dependency updates (#1015, #1005).

  • Docker: prest/prest:v2.4.2

  • Go install: go install github.com/prest/prest/v2/cmd/prestd@v2.4.2

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


Highlights

Minimum HMAC key sizes for jwt.key (#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:

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.

jwt.algo is now enforced (#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)

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:

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, where v2.4.1 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:

    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.

Credential headers withheld from templates (#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 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)

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:

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)

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) and google.golang.org/grpc 1.81.1 β†’ 1.82.1 (#1005). Both are dependency-only with no source changes; JWKS handling and OpenTelemetry export behave as before.


Changes since v2.4.1

PR
Summary

RFC 7518 minimum HMAC key sizes for jwt.key; go-jose/v4 migration; jwt.algo enforced as the sole permitted signature algorithm

Credential headers withheld from script templates; rejected interpolated values fail with 400; script SQL removed from logs; script path traversal rejected; #1030 screen relaxation; CI workflow permissions

Bump github.com/lestrrat-go/jwx/v3 3.1.1 β†’ 3.2.0

Bump google.golang.org/grpc 1.81.1 β†’ 1.82.1

Full detail: compare v2.4.1...v2.4.2. Coming from v2.4.0, see also Changes since v2.4.0.


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 as well β€” MCP discovery changes when [expose] is active.


Last updated