Cloud container: the production PocketBase's superuser and _system collections are closed on /api-prod too

/pb already refused them to anyone who is not an admin. /api-prod, the production PocketBase's
public API, passed every /api/ path, so its superuser sign-in was reachable from the public side:
no data was exposed, but it was a place to guess the production admin's password. Now any
/api-prod/api/collections/_… path is refused (403), checked on the decoded path like the /pb gate.
No public client calls these; the one caller in the repo (pb-oidc.mjs) talks to PocketBase directly.
Found by dx-base's independent review of the public Studio (V2).
This commit is contained in:
Doxa Maps 2026-09-29 05:25:03 +00:00
parent 28c4bacd2a
commit 097952b5f9
2 changed files with 4 additions and 2 deletions

View file

@ -92,7 +92,7 @@ With the sign-in on, the container also runs PocketBase and can publish a produc
|---|---|---|
| `/` | signed-in | the dev server (Plugin Studio, hot reload) |
| `/pb/` | signed-in (its `/_/` dashboard: `OIDC_ADMIN_GROUPS` only). With `PB_OIDC_CLIENT_SECRET`, its `/api/` is public and PocketBase signs people in itself (see below); `_superusers` and system collections stay admin-only | PocketBase **dev**, the team's shared backend |
| `/api-prod/api/` | **public** | PocketBase **prod**'s API; its dashboard is never exposed |
| `/api-prod/api/` | **public** | PocketBase **prod**'s API; its dashboard, its superuser and its `_` system collections are never exposed |
| `/cdn/dx-maps/` | **public** | the last published build: `manifest.json` at a stable URL, CORS open for embeds |
| `POST /admin/build` | `OIDC_ADMIN_GROUPS` only | build and publish the pulled commit |
| `GET /admin/status` | signed-in | the published and pulled commits, and whether a build is running |

View file

@ -1,6 +1,6 @@
// admin.mjs — the container's internal router (127.0.0.1:5175), behind oauth2-proxy.
// /cdn/* the last good build (/data/cdn/current), PUBLIC (oauth2-proxy skips auth here)
// /api-prod/* PocketBase PROD, PUBLIC; only /api/ passes, never the /_/ dashboard
// /api-prod/* PocketBase PROD, PUBLIC; only /api/ passes, never the /_/ dashboard, _superusers or _system collections
// /pb/* PocketBase DEV, signed-in; its /_/ dashboard only for OIDC_ADMIN_GROUPS. With PB_OIDC_CLIENT_SECRET,
// its /api/ is PUBLIC (PocketBase signs people in itself), except _superusers and _system collections
// /admin/build POST: build PRODUCTION_BRANCH now (OIDC_ADMIN_GROUPS only); /admin/status GET.
@ -178,6 +178,8 @@ Bun.serve({
const bp = backendPath(p)
if (bp === null) return text('bad path', 400)
if (!p.startsWith('/api-prod/api/') || !bp.startsWith('/api-prod/api/')) return text('only /api/ is public', 403)
// As on /pb: the superuser and every _system collection stay closed. No public client uses them (dx-base, V2 review).
if (bp.startsWith('/api-prod/api/collections/_')) return text('for admins only', 403)
return proxy(req, url, '/api-prod/', 8091)
}
if (p.startsWith('/pb/')) {