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:
- 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.
- 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).
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 byreject_off_allowlist_fetches) rejects, at the request boundary before any fetch, anyregistry/namedRegistriesor 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:
registry.npmjs.org) can declare a transitive dependency on an off-allowlisthttp(s)/git URL — includinghttp://169.254.169.254/...(IMDS). pacquet's tarball/git resolvers fetch it server-side during the tree walk, outside the request boundary.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
http://169.254.169.254/), which skip DNS entirely.gitbinary, 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-rebindinghttp(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).