You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today pnpm keeps the Authorization header when a registry redirect stays on the same host, even if the redirect downgrades the scheme from https to http. The credential is then sent again over cleartext.
The comparison lives in @pnpm/network.fetch (src/fetchFromRegistry.ts):
URL.host is hostname + port and excludes the scheme, so https://registry.example/x -> http://registry.example/x counts as "same destination" and the bearer/basic credential is replayed in the clear.
This was reported to us privately as a vulnerability. We do not consider it one, and we are opening it here so the tradeoff can be discussed in public.
Why we do not treat it as a pnpm vulnerability
No attacker gains a credential they did not already have. To trigger it you must make the registry return 302 Location: http://<same-host>/... for a request the victim sent over HTTPS. That requires either:
Being the registry (malicious or compromised) — but the victim already sent you the Authorization header on the first leg. The redirect adds nothing.
Being an active on-path attacker — but the first leg is HTTPS, and injecting a redirect into a TLS-protected response is what TLS prevents. That needs a valid certificate for the host, or strict-ssl=false, at which point the credential is obtainable more directly.
The only case with any gain is a split: one party can influence the registry's response (compromised CDN edge, an open redirect on the registry) while a different, passive party observes the victim's network.
Every other client behaves the same way. This is not pnpm failing to enforce a boundary it built — the host-based rule is what the ecosystem implements:
Implementation
Strips Authorization when…
npm — make-fetch-happen/lib/fetch.js
hostname differs; the comment is explicit: "Remove authorization if changing hostnames (but not if just changing ports or protocols)"
npm — minipass-fetch/lib/index.js
hostname differs
Yarn — got core
hostname or port differs; protocol ignored
reqwest (Rust clients)
host/port differ; protocol ignored
Browsers / fetch spec
full origin differs, scheme included
Only browsers are origin-based. A registry that redirects from HTTPS to plaintext HTTP on the same host is the broken component here, not the client that follows it.
Why we have not simply changed it
Comparing full origins would strip the credential on the http -> httpsupgrade on the same host as well. Self-hosted registries configured with an http:// URL that redirect to HTTPS work today and would start returning 401. That is a real behavior break for a change with a narrow security benefit.
A narrower variant is possible: keep the host comparison and strip the header only when the scheme downgrades from https to http, leaving the upgrade path and http -> http untouched. The realistic group affected by even that variant is registries behind a TLS-terminating proxy that emits Location: with the wrong scheme — a common misconfiguration. Those setups work today, and would start failing with 401. Note that such a setup is already sending the registry token in plaintext on every install, so the change converts a silent credential leak into a loud failure.
Questions for discussion
Should pnpm strip Authorization on a same-host https -> http redirect, accepting that registries behind a misconfigured TLS-terminating proxy would break?
If yes, should it be unconditional, or gated behind a setting (or tied to strict-ssl)?
Is the loud 401 preferable to today's silent cleartext transmission for those users?
If you operate a registry that relies on a same-host scheme-downgrading redirect, please say so here — that is the group a change would affect.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Today pnpm keeps the
Authorizationheader when a registry redirect stays on the same host, even if the redirect downgrades the scheme fromhttpstohttp. The credential is then sent again over cleartext.The comparison lives in
@pnpm/network.fetch(src/fetchFromRegistry.ts):URL.hostis hostname + port and excludes the scheme, sohttps://registry.example/x->http://registry.example/xcounts as "same destination" and the bearer/basic credential is replayed in the clear.This was reported to us privately as a vulnerability. We do not consider it one, and we are opening it here so the tradeoff can be discussed in public.
Why we do not treat it as a pnpm vulnerability
No attacker gains a credential they did not already have. To trigger it you must make the registry return
302 Location: http://<same-host>/...for a request the victim sent over HTTPS. That requires either:Authorizationheader on the first leg. The redirect adds nothing.strict-ssl=false, at which point the credential is obtainable more directly.The only case with any gain is a split: one party can influence the registry's response (compromised CDN edge, an open redirect on the registry) while a different, passive party observes the victim's network.
Every other client behaves the same way. This is not pnpm failing to enforce a boundary it built — the host-based rule is what the ecosystem implements:
Authorizationwhen…make-fetch-happen/lib/fetch.jsminipass-fetch/lib/index.jsgotcoreOnly browsers are origin-based. A registry that redirects from HTTPS to plaintext HTTP on the same host is the broken component here, not the client that follows it.
Why we have not simply changed it
Comparing full origins would strip the credential on the
http->httpsupgrade on the same host as well. Self-hosted registries configured with anhttp://URL that redirect to HTTPS work today and would start returning 401. That is a real behavior break for a change with a narrow security benefit.A narrower variant is possible: keep the host comparison and strip the header only when the scheme downgrades from
httpstohttp, leaving the upgrade path andhttp->httpuntouched. The realistic group affected by even that variant is registries behind a TLS-terminating proxy that emitsLocation:with the wrong scheme — a common misconfiguration. Those setups work today, and would start failing with 401. Note that such a setup is already sending the registry token in plaintext on every install, so the change converts a silent credential leak into a loud failure.Questions for discussion
Authorizationon a same-hosthttps->httpredirect, accepting that registries behind a misconfigured TLS-terminating proxy would break?strict-ssl)?If you operate a registry that relies on a same-host scheme-downgrading redirect, please say so here — that is the group a change would affect.
Written by an agent (Claude Code, claude-opus-5).
All reactions