Two holes studio found in admin.mjs, there since 2026-09-27:
- proxy() parsed the remaining path as a URL. /pb//evil.example/x is a protocol-relative URL, so
admin.mjs fetched ANOTHER host (any internal address too) with the visitor's own headers, and
returned its body. The path now goes onto a fixed http://127.0.0.1:<port> origin and is never
parsed as a URL, and a path starting with // is refused.
- The /pb/_/ and _superusers checks tested the raw path, so /pb/%5F/ and
/pb/api/collections/%5Fsuperusers/... passed them, and PocketBase decoded them to the real
thing. The checks now run on the decoded, slash-collapsed path. Encoded slashes or
backslashes, and undecodable input, get 400.
Reach today: maps-dev answers all four attack paths with its sign-in page to an anonymous
visitor (measured), so a signed-in team member could use them. In ACCESS=public they would
be anonymous; that mode is held until this lands.
Checked both ways against a PocketBase-like upstream plus an "evil" host. The old code let
/pb/%5F/, %5Fsuperusers and %2F through, fetched http://_/ and hit the evil host once. The new
code refuses them (403/400) with 0 evil hits, and health and records calls still pass.