Skip to content

pnpr: resolver fetches against client-supplied registries are an SSRF primitive when exposed to untrusted clients #12564

Description

@zkochan

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions