Which area(s) of pnpm are affected?
Configuration, Dependencies resolver
pnpm version
- pnpm 12.7.0 and a build of
main: accepts the value, sends it as the raw password
- pnpm 11.28.0: refuses to run
Found while fixing the credential selection in #10237. The two CLIs disagree on a _password that is not valid base64, and npm agrees with neither.
Reproduction steps
mkdir repro && cd repro
cat > package.json <<'JSON'
{ "name": "repro", "version": "1.0.0", "private": true, "dependencies": { "is-number": "7.0.0" } }
JSON
cat > .npmrc <<'INI'
registry=https://registry.example.com/npm/
//registry.example.com/npm/:username=u
//registry.example.com/npm/:_password=notbase64!
INI
pnpm install
notbase64! is not valid base64. npm documents _password as the base64-encoded password, so a valid file holds _password=cGFzc3dvcmQ= for password.
Describe the Bug
The same .npmrc produces three different credentials depending on the client, and pnpm 11 does not reach the registry at all:
| Client |
Behavior |
Authorization sent |
pnpm 12.7.0, and main |
accepts the value and uses it verbatim |
Basic dTpub3RiYXNlNjQh, decoding to u:notbase64! |
| pnpm 11.28.0 |
fails before any request |
none, [ERROR] Failed to decode _password as base64 |
| npm 11 |
decodes leniently and sends the mangled result |
Basic dTrvv73vv71base6, decoding to u: plus garbage bytes |
The npm row is the one that looks accidental: its forgiving base64 decoder drops the invalid characters and keeps low bits of the truncated final group, so the registry receives a password that is neither the configured value nor a valid decoding of it.
Code
- pnpm 11:
pnpm11/config/reader/src/parseCreds.ts — decodeBase64Credential throws AuthBase64DecodeError, reached from parseBasicAuth for username + _password.
- pnpm 12:
pnpm/crates/config/src/npmrc_auth/credentials.rs — base64_decode(pass_b64).unwrap_or_else(|| pass_b64.clone()) treats a value that fails to decode as the raw password.
Expected Behavior
Two of these three are defensible, but they should not be three:
- Reject, the way pnpm 11 and the npm documentation do. A typo in
_password fails at config load with a clear error instead of authenticating as a password nobody chose.
- Decode leniently, the way npm does, accepting that a malformed value yields a malformed credential.
Passing the value through raw is the one behavior with no precedent: it makes a base64-only setting accept a non-base64 value by quietly changing its meaning.
Which Node.js version are you using?
24.x
Which operating systems have you used?
Written by an agent (opencode, space-bunny-free).
Which area(s) of pnpm are affected?
Configuration, Dependencies resolver
pnpm version
main: accepts the value, sends it as the raw passwordFound while fixing the credential selection in #10237. The two CLIs disagree on a
_passwordthat is not valid base64, and npm agrees with neither.Reproduction steps
notbase64!is not valid base64. npm documents_passwordas the base64-encoded password, so a valid file holds_password=cGFzc3dvcmQ=forpassword.Describe the Bug
The same
.npmrcproduces three different credentials depending on the client, and pnpm 11 does not reach the registry at all:AuthorizationsentmainBasic dTpub3RiYXNlNjQh, decoding tou:notbase64![ERROR] Failed to decode _password as base64Basic dTrvv73vv71base6, decoding tou:plus garbage bytesThe npm row is the one that looks accidental: its forgiving base64 decoder drops the invalid characters and keeps low bits of the truncated final group, so the registry receives a password that is neither the configured value nor a valid decoding of it.
Code
pnpm11/config/reader/src/parseCreds.ts—decodeBase64CredentialthrowsAuthBase64DecodeError, reached fromparseBasicAuthforusername+_password.pnpm/crates/config/src/npmrc_auth/credentials.rs—base64_decode(pass_b64).unwrap_or_else(|| pass_b64.clone())treats a value that fails to decode as the raw password.Expected Behavior
Two of these three are defensible, but they should not be three:
_passwordfails at config load with a clear error instead of authenticating as a password nobody chose.Passing the value through raw is the one behavior with no precedent: it makes a base64-only setting accept a non-base64 value by quietly changing its meaning.
Which Node.js version are you using?
24.x
Which operating systems have you used?
Written by an agent (opencode, space-bunny-free).