Skip to content

pnpr: connect-time guard for transitive-dependency / DNS-rebinding SSRF #12705

Description

@zkochan

Follow-up hardening for the pnpr resolver, tracked out of #12700 (whose default-deny fetch allowlist closes the direct-input and redirect SSRF vectors).

Background

The resolver's fetch allowlist (RouteContext::allows_registry, enforced by reject_off_allowlist_fetches) rejects, at the request boundary before any fetch, any registry/namedRegistries or direct-URL dependency spec / override / input-lockfile tarball whose origin isn't operator-configured. The reqwest redirect policy (new_for_installs_with_redirect_guard) re-validates every redirect hop against the same allowlist.

The gap

Two SSRF residuals are not covered by an origin-level, request-boundary allowlist:

  1. Transitive dependencies. The boundary check only sees the client's inputs. A package resolved from an allowlisted registry (e.g. anyone's package on registry.npmjs.org) can declare a transitive dependency on an off-allowlist http(s)/git URL — including http://169.254.169.254/... (IMDS). pacquet's tarball/git resolvers fetch it server-side during the tree walk, outside the request boundary.
  2. DNS rebinding. An allowlisted hostname that resolves to a link-local/internal address only at connect time.

This is mostly a blind SSRF — pnpr fetches the URL but does not echo the response to the caller — but blind SSRF to internal services / IMDS is still a real risk.

Why the allowlist can't close it

  • A DNS-resolver-level host allowlist misses IP-literal URLs (http://169.254.169.254/), which skip DNS entirely.
  • git transitive deps are resolved via the git binary, not the shared reqwest client, so a client-level guard doesn't see them.

Proposed fix

A connector-level guard that filters the actual connect target IP — blocking link-local / private / loopback / metadata ranges (169.254.0.0/16, 10.0.0.0/8, 127.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, the IMDS endpoint) for every outbound connection of pnpr's resolution client. This closes the transitive and DNS-rebinding http(s) vectors at once, including IP-literal URLs. git remotes need their own gate (e.g. validating each transitive git dependency's origin against the allowlist before resolving it, or refusing transitive git deps to non-allowlisted hosts).

The CLI client is unaffected (it fetches on the user's own behalf, so there's no SSRF surface).

See the "Fetch allowlist and SSRF" section of the RFC (pnpm/rfcs#11) for the full model and where this residual is documented.


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