pREST v2.5.2 fixes three advisories that hit every release up to v2.5.1. The worst, CVSS 9.0, turns two unauthenticated requests into a shell command on your database host.
Upgrading is one retag plus a few checks. Below: how to tell if you’re exposed, what changes, and the exact steps.
pREST v2 series: Architecture (rc6) → v2.0.0 / v2.1.0 GA → MCP tutorial → AI plugins → v2.2.0 → v2.3.0 → v2.4.0 → v2.4.1 + v2.4.2 → v2.5: MySQL → This post (v2.5.2)
New to pREST? The pREST v2 guide lists every release and where to start.
Upgrade first. Pull
prest/prest:v2.5.2. Two fixes can break clients. Tables namedprest_usersorprest_queriesare no longer reachable over REST. With auth on, the catalog routes now need a token.
Table of Contents
- Are you affected?
- What v2.5.2 fixes
- How to upgrade
- Replace pg.single
- Versions and limits
- Also in this release
- FAQ
- Sources
Are you affected?
Every release up to v2.5.1 is affected. How badly depends on your config:
| Your setup on v2.5.1 or earlier | Exposed to |
|---|---|
queries.storage = "database", auth off |
Arbitrary SQL with no credentials, and OS commands if pREST connects as superuser |
queries.storage = "database", auth on, restrict = false |
The same, for anyone holding a valid token |
auth.enabled = true |
Your database, table and column map, readable without a token |
/_PLUGIN reachable, JWT not enforced |
Loading any .so file on the server’s disk |
| PostgreSQL | /databases listing every database on the server, not just yours |
The official compose files connect as the PostgreSQL superuser. That is what turns stored SQL into code execution.
What v2.5.2 fixes
Stored queries were writable over REST
GHSA-2x9q-r7j9-g85f, critical, CVSS 9.0, reported by arkmarta.
With database-backed queries, script bodies live in prest_queries. Nothing stopped the generic table routes from writing it. The advisory’s proof of concept, with no credentials:
# store SQL in the queries table
curl -s -X POST http://target:3000/prest/public/prest_queries \
-H 'Content-Type: application/json' \
-d '{"location":"rpt","name":"d","write_sql":"DO $$ ... $$"}' # DO block wrapping COPY ... FROM PROGRAM 'id'
# HTTP/1.1 201 Created
# run it, then read the output
curl -s -X POST http://target:3000/_QUERIES/rpt/d
curl -s http://target:3000/prest/public/p
# [{"l": "uid=999(postgres) gid=999(postgres) groups=999(postgres)"}]
Now prest_queries, prest_users and your configured queries.table and auth.table are reserved. Any schema, any case, no opt-out:
| Request | v2.5.2 response |
|---|---|
Any method on /{db}/{schema}/{reserved}, /batch, /show |
401 {"error":"authorization required"}, even for admins |
_join to a reserved table |
400 {"error":"table is reserved for pREST internal use"} |
MCP select_table / describe_table |
JSON-RPC permission error |
Manage stored queries through the admin-only /_QUERIES/registry API.
The catalog skipped auth
GHSA-fhf5-m388-4w4p, high, CVSS 7.5, reported by Hama1cco.
The catalog routes were registered outside the auth middleware. With PREST_AUTH_ENABLED=true and no token:
GET /prest/public/users → 401 (CRUD, denied)
GET /tables → 200 every table and owner
GET /show/prest/public/users → 200 every column, type and default
Now /databases, /schemas, /tables, /{database}/{schema} and /show/... run auth and table permissions. No token returns 401 {"error":"authorization token is empty"}. /show follows [access] even with auth off. /databases lists only the databases pREST serves.
Plugin names escaped pluginpath
GHSA-fmr6-9j48-xrqp, high, CVSS 8.1, reported by T4ran24.
/_PLUGIN/{file}/{func} joined {file} onto pluginpath unchecked. A {file} of ../../../../tmp/sample loaded /tmp/sample.so. The advisory’s test returned {"pwned":true}.
Now {file} must be letters, digits, _ or -, with no dots. The resolved path must stay inside pluginpath, symlinks included. A bad name returns 400 {"error": "invalid plugin name"}. With auth on, the route needs a token.
How to upgrade
- Retag and restart:
docker pull prest/prest:v2.5.2
# or
go install github.com/prest/prest/v2/cmd/prestd@v2.5.2
- Audit stored queries if you used database storage. The patch blocks new writes but keeps planted rows:
SELECT location, name, created_by, updated_at,
read_sql, write_sql, update_sql, delete_sql
FROM public.prest_queries
ORDER BY updated_at DESC;
Read the SQL columns, not created_by; a REST write could set it to anything. Delete rows you didn’t create.
- Check whether pREST connects as a superuser:
SELECT rolsuper FROM pg_roles WHERE rolname = current_user;
If it returns t, switch to a role without superuser or pg_execute_server_program. That role can’t run COPY ... FROM PROGRAM.
Rename any app table called
prest_usersorprest_queries.Give catalog clients a token, or add the route to
[jwt] whiteliston purpose:
curl -s localhost:3000/tables -H "Authorization: Bearer $TOKEN"
- Find plugin files that the new name rules reject:
find "$PLUGIN_PATH" -name '*.so' | grep -E '/[^/]*\.[^/]*\.so$' # dots in the name
find "$PLUGIN_PATH" -type l # symlinks to check
- Replace
pg.single(next section).
Full list: v2.5.2 release notes.
Replace pg.single with pg.allowed_databases
pg.single and PREST_PG_SINGLE are now ignored. Startup logs:
config key ignored: pg.single was removed; use [[databases]] or pg.allowed_databases
A [[databases]] registry now serves all its aliases. Without a registry, list extra databases on the same host:
[pg]
database = "prest"
allowed_databases = ["reporting", "archive"]
PREST_PG_ALLOWED_DATABASES=reporting,archive
Any other database name returns 400 database not registered: <name>. On MySQL without a registry, {schema} must equal {database}, or you get a 404.
Versions and limits
- Affected: every release up to v2.5.1. Fixed: v2.5.2.
- With auth off,
/_PLUGINstays open. Only the path checks apply. - Upgrading doesn’t remove planted stored queries. Step 2 does.
- Reserved table names have no opt-out.
Also in this release
The multi-database rework (#1068) was co-authored by @samithiwat, starting from the brickstechnology/prest fork. An unreachable [[databases]] entry no longer stops startup; it answers 503 and reconnects on first use. prestd version reports the real version on go install builds. golang.org/x/crypto moves to 0.58.0.
FAQ
Is pREST v2.5.1 vulnerable?
Yes. All three advisories affect every release up to v2.5.1. v2.5.2 fixes them. The critical one needs queries.storage = "database".
How do I check if someone planted a stored query in pREST?
Query prest_queries and read the four SQL columns of every row. Don’t trust created_by, since a REST write could set it. Delete what you didn’t create.
Why does /databases return 401 after upgrading pREST?
Since v2.5.2, catalog routes use the same auth as CRUD routes. With auth on, send a bearer token. Or whitelist the route if it should be public.
What replaces pg.single in pREST?
A [[databases]] registry, or pg.allowed_databases without one. pg.single is ignored from v2.5.2, with a warning at startup.
Can I still name a table prest_users in pREST?
Not if you want it reachable through pREST. Those names are reserved on REST, _join and MCP. Rename the table.
Does turning on pREST auth protect plugins?
Since v2.5.2, yes: /_PLUGIN needs a token when auth is on. With auth off it stays open. Name and path checks apply either way.
Sources
Release: v2.5.2 on GitHub. Release notes: docs.prestd.com. Advisories: GHSA-2x9q-r7j9-g85f, GHSA-fhf5-m388-4w4p, GHSA-fmr6-9j48-xrqp. Fixes: #1070, #1068.
Related: pREST in 10 minutes, pREST v2.5 with MySQL, pREST v2 guide.