mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-14 17:52:44 -04:00
Make the pnpr resolver cache authorization-aware and route private dependencies through per-uplink registry endpoints. Add route classification at the single fetch/auth-selection point: pnpr selects its own server-owned upstream credentials instead of forwarding client upstream auth, records a per-resolve footprint, and rejects inline URL credentials. A uplinks: entry that declares an access: policy becomes the private-route credential (folding in the former upstreamAliases block), matched by registry origin and exposed as a read-only registry endpoint at /~<uplink>/. access reuses bearer-token-backed pnpr identities, package access policy, and static groups; a SHA-256 digest of the uplink's credential participates in the footprint, so rotating the upstream credential automatically moves new resolves to a fresh namespace. The credential is attached only over a matching scheme (no token over plain http). Gate every server-side fetch behind a default-deny allowlist (built-in npm host, public routes, configured uplinks and their /~<uplink>/ endpoints, and pnpr itself). A registry/namedRegistries matching none is rejected at the request boundary, closing the resolver's SSRF surface at the source and superseding the link-local denylist. The boundary also covers direct-URL (http(s)/git, incl. scp-style) dependency specs, overrides, and lockfile tarballs; a `..` path segment is rejected; and the same allowlist re-validates every redirect hop. The official npm registry is a built-in host-level public route (scoped names included), so the npmjsPublic toggle is gone. With no off-allowlist route to resolve, every route is public or carries a private descriptor: RouteClass::Unknown, the non-shareable footprint tier, and the MetadataCacheScope::Bypass tier are removed. Transitive deps fetched during the tree walk are a connect-time-guard follow-up (pnpm/pnpm#12705). Route resolver tarball URLs through those endpoints. A proxied route emits its /~<uplink>/ endpoint URL; public routes keep their upstream URL (fetched directly from the registry/CDN); pnpr-hosted packages use pnpr-hosted URLs. An endpoint URL is canonical for a client whose scope points there, so the lockfile entry stays integrity-only and the host comes from the client's registry config rather than the lockfile — a project resolves to the same lockfile through /resolve or a direct/proxied install. verification_lockfile reverses endpoint URLs to upstream for input-lockfile verification, and classification recognizes pnpr's own /~<uplink>/ URLs. The opaque per-tarball gateway scheme and its in-memory key->URL map are removed. Serve each access-bearing uplink as a /~<uplink>/ registry endpoint (packument + tarball, gated by the uplink access policy) with a private cache namespaced by an HMAC of (uplink, credential), so a private install caches like a public one, a rotation re-keys automatically, and a private uplink's content never lands in the shared mirror. Store bounded candidate lists under an auth-excluded base key instead of a single lockfile. Public candidates match every caller; private candidates carry the footprint and descriptor HMAC and are reused only when the caller still satisfies the stored uplink-or-hosted gates. Compute the base key for both no-lockfile and lockfile-seeded requests, using hash_lockfile() for input lockfiles, and evict expired/LRU private candidates before public ones. Record metadata fast-path routes into the footprint. The hook only fires at the auth-selection point, which the npm resolver's metadata fast paths (in-memory hit, offline disk read, version-spec exact match, publishedBy mtime shortcut) bypass. AuthHeaders::record_route drives the hook without a request, called up front in pick_package so every layer contributes to the footprint. Scope the npm metadata mirror by private access descriptor. A MetadataCacheScope (Public / Private) classified per (registry, package) fetch threads through pick_package, fetch_full_metadata_cached, the mirror path, in-memory/fetch-lock keys, and the verifier's local-mirror read. A private route stores its packument under v11/metadata-private/<descriptor-id>/. Fail closed on 401/403/private-404. The CLI installs no hook, so every fetch stays Public and the global mirror is unchanged. Related to pnpm/pnpm#12699 and pnpm/rfcs#11.