Summary
pnpr's resolver endpoint (POST /v1/resolve, and POST /v1/verify-lockfile) resolves against the registries supplied in the client request (registry, namedRegistries) and performs server-side outbound HTTP GETs to URLs derived from those inputs. There is no allowlist or network-range restriction on those base URLs, so if the resolver surface is reachable by untrusted clients it is an SSRF primitive (probing internal hosts, cloud metadata endpoints, etc.).
Where
serve_resolve calls handle_resolve with no auth gate.
Resolver::config_for sets pacquet's config.registry / config.named_registries directly from request fields.
- pacquet's npm resolver builds the metadata URL as
<registry>/<encoded-pkg> and issues client.get(url) (pacquet/crates/resolving-npm-resolver).
Notes / context
- This is the resolver's documented design — it intentionally resolves against the client's registries so the server uses the same source of truth as the client, and forwards the caller's credentials for private packages. So this is a hardening item, not a regression.
- The feature-toggle work (
registry/resolver surfaces) does not change resolution-fetch behavior; it only gates which surfaces are mounted. But the new resolver-only deployment shape makes it more likely the resolver is exposed as a standalone, internet-facing tier, which amplifies the impact — hence filing this.
Possible mitigations to consider
- An outbound registry allowlist (config: permitted registry hosts/URLs); reject requests whose
registry/namedRegistries fall outside it.
- SSRF range-blocking: refuse resolution against private/loopback/link-local IP ranges (and cloud metadata IPs) unless explicitly allowed.
- Auth-gating the resolver surface (require an
Authorization accepted by pnpr's gate before resolving), so it isn't anonymously reachable.
- Documentation: make clear that a publicly-exposed resolver tier should sit behind an authenticating proxy / network policy.
Written by an agent (Claude Code, claude-opus-4-8).
Summary
pnpr's resolver endpoint (
POST /v1/resolve, andPOST /v1/verify-lockfile) resolves against the registries supplied in the client request (registry,namedRegistries) and performs server-side outbound HTTP GETs to URLs derived from those inputs. There is no allowlist or network-range restriction on those base URLs, so if the resolver surface is reachable by untrusted clients it is an SSRF primitive (probing internal hosts, cloud metadata endpoints, etc.).Where
serve_resolvecallshandle_resolvewith no auth gate.Resolver::config_forsets pacquet'sconfig.registry/config.named_registriesdirectly from request fields.<registry>/<encoded-pkg>and issuesclient.get(url)(pacquet/crates/resolving-npm-resolver).Notes / context
registry/resolversurfaces) does not change resolution-fetch behavior; it only gates which surfaces are mounted. But the new resolver-only deployment shape makes it more likely the resolver is exposed as a standalone, internet-facing tier, which amplifies the impact — hence filing this.Possible mitigations to consider
registry/namedRegistriesfall outside it.Authorizationaccepted by pnpr's gate before resolving), so it isn't anonymously reachable.Written by an agent (Claude Code, claude-opus-4-8).