Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: download the latest version instead of a pinned one #134

Merged
merged 7 commits into from
Aug 19, 2022

Conversation

aduh95
Copy link
Contributor

@aduh95 aduh95 commented Jun 26, 2022

This PR ensures that fresh install of Corepack would always use the latest available version for a given package manager.

We probably want to add a command to clear the cache for lastKnownGood version (although I think that's more or less what corepack prepare is doing), and/or have a expiracy date for the lastKnownGood version to make Corepack update the default version from time to time.

Fixes: #93

@aduh95 aduh95 requested a review from arcanis June 26, 2022 13:18
sources/Engine.ts Outdated Show resolved Hide resolved
sources/Engine.ts Outdated Show resolved Hide resolved
@aduh95 aduh95 marked this pull request as ready for review June 29, 2022 18:24
@aduh95 aduh95 requested a review from arcanis June 30, 2022 11:45
README.md Outdated
@@ -106,6 +113,8 @@ This command will retrieve the given package manager from the specified archive

- `COREPACK_ENABLE_NETWORK` can be set to `0` to prevent Corepack from accessing the network (in which case you'll be responsible for hydrating the package manager versions that will be required for the projects you'll run, using `corepack hydrate`).

- `COREPACK_NO_LOOKUP` can be set in order to instruct Corepack not to lookup on the remote registry for the latest version of the selected package manager.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably find a better name for this env variable, I don't think one could infer what it's doing from the name only. Does anyone has suggestions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand the difference between COREPACK_ENABLE_NETWORK=0 and COREPACK_NO_LOOKUP=1? In theory I would expect no network to behave the same since corepack ships with Known Good Versions but I must be overlooking a use case here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When COREPACK_NO_LOOKUP is set, Corepack will use the version defined in config.json instead of asking the npm registry for the most up-to-date version. COREPACK_ENABLE_NETWORK can disable all access to the network, both for finding what's the most up-to-date version number and to download the tarball.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is config.json available to configure by the user of corepack or is it just internals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only internals, bundled in the corepack executable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about COREPACK_IGNORE_LATEST?

Copy link
Contributor Author

@aduh95 aduh95 Jul 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with COREPACK_DEFAULT_TO_LATEST which is true by default, and is set to 0 to use the value in config.json instead.

Copy link
Member

@styfle styfle Jul 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about using “default” as a verb here.

How about COREPACK_ENABLE_FETCH_LATEST=0

Copy link
Contributor Author

@aduh95 aduh95 Jul 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like the naming to make clear that it's only relevant if there's no local clue on what version to use. ENABLE_FETCH_LATEST could mean that the latest is always downloaded, or at least that's how I read it.

Not sure about using “default” as a verb here.

I'm not a native english speaker, so genuine question: is it incorrect or something to use "default" as a verb?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s not incorrect, just that “default” can be a verb or a noun. It’s probably fine 👍


The Known Good Releases can be updated system-wide using the `--activate` flag from the `corepack prepare` and `corepack hydrate` commands.
If there is no Known Good Release for the requested package manager, Corepack
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case would there be no Known Good Release?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon the first time Corepack is run with a given package manager. The idea behind this PR would be not to rely on what version is set on config.json but instead get the most up-to-date version from npm registry.

Copy link
Member

@styfle styfle Jul 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often does it check the registry? Does it check every time you invoke yarn/pnpm/npm because that seems slow. Especially if I just want to do npm run <script> which should work offline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, only the first time, at which point it saves it as Known Good Release. Users can then update (or downgrade) that version by using corepack prepare or corepack hydrate (e.g. corepack prepare pnpm@latest --activate to get the latest pnpm by default). We could make an automated process for the Known Good Version to expire after some time, but that's not part of this initial implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I understand now 👍

I think expiring would could be confusing. I also think this any auto update could be confusing because it no longer ties the package manager version to the corepack version (or node version for that matter).

I think it’s best to let the user decide when to upgrade and perhaps provide better docs how to upgrade (this isn’t super clear today). Without corepack, this is handled with a notification that says something like npm i -g npm@latest.

Has any of this behavior been reviewed by the npm team? I think they had some ideas about Know Good Release.

Copy link
Contributor Author

@aduh95 aduh95 Jul 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this any auto update could be confusing because it no longer ties the package manager version to the corepack version (or node version for that matter).

Well that's the reason Corepack exists, because the Node.js TSC doesn't want to have specific version of a package manager tied in with a specific Node.js version – specifically, several Node.js TSC members have expressed that the reason they are supportive of Corepack is to get rid of having to do security releases every time a vulnerability is reported in npm rather than node itself.
So the auto-lookup during the first use has to be the default behavior.

I think it’s best to let the user decide when to upgrade and perhaps provide better docs how to upgrade (this isn’t super clear today). Without corepack, this is handled with a notification that says something like npm i -g npm@latest.

Yes that makes sense, letting the end-user in control is very much the goal here. So instead of npm i -g npm@latest, users would type corepack prepare npm@latest --activate, which is arguably quite similar. Improving docs is of course always a goal.

Has any of this behavior been reviewed by the npm team? I think they had some ideas about Know Good Release.

The npm team hasn't shown much interest in the development of Corepack, so I don't think they have.

export async function fetchLatestStableVersion(spec: RegistrySpec) {
switch (spec.type) {
case `npm`: {
const {[`dist-tags`]: {latest}, versions: {[latest]: {dist: {shasum}}}} =
Copy link
Member

@styfle styfle Jul 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this consider the major version? What about when npm 7 bumped to npm 8 to drop support for node 12. Imagine if corepack existed then and suddenly npm stopped working because of the automatic fetch latest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s worth discussing, but imo it’s ok to ignore that for the initial implementation.

Copy link
Contributor

@arcanis arcanis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's acceptable to me (Yarn isn't impacted that much since we don't make very frequent / risky releases to 1.22 anyway). Maybe we should wait a bit to see if @zkochan has an opinion?

@aduh95 aduh95 merged commit 055b928 into main Aug 19, 2022
@aduh95 aduh95 deleted the dynamic-default-version branch August 19, 2022 21:53
@zkochan
Copy link

zkochan commented Aug 20, 2022

Sorry for not answering. I think it is OK to ship the latest pnpm. Thanks

@andersk
Copy link

andersk commented Aug 23, 2022

I have some concerns about this change.

  1. This negates the hash protection of chore: add sha1 to default versions when available #137.

  2. This could break the reproducibility of builds or CI jobs that start from a specific Node.js version (unless specifically overridden).

  3. Consider a user who gets semi-regular automatic Node.js updates from their distribution, which means they get semi-regular automatic corepack updates. Under the previous scheme, they would therefore get semi-regular automatic updates to the default versions of Yarn and pnpm. But now those versions will be fixed from the first time they’re run (until a manual update).

@aduh95
Copy link
Contributor Author

aduh95 commented Aug 24, 2022

  1. This negates the hash protection of chore: add sha1 to default versions when available #137.
  2. This could break the reproducibility of builds or CI jobs that start from a specific Node.js version (unless specifically overridden).

The workaround is to set a specific version in the project package.json and/or set COREPACK_DEFAULT_TO_LATEST to 0 in the CI env.

3. Consider a user who gets semi-regular automatic Node.js updates from their distribution, which means they get semi-regular automatic corepack updates. Under the previous scheme, they would therefore get semi-regular automatic updates to the default versions of Yarn and pnpm. But now those versions will be fixed from the first time they’re run (until a manual update).

Contributions are welcome to improve this workflow.

@andersk
Copy link

andersk commented Aug 24, 2022

Of course there are workarounds. But good defaults matter, and I’m proposing that the default should remain the secure known-good version.

A user who discovers they need a later version than the known-good version is in a position to go read the corepack documentation and configure it appropriately. (Most likely what they really want is to set a version in package.json so that everyone working on that project gets the right version. But if they want to set it system-wide, sure, they can have that option.)

Meanwhile, a user who unexpectedly gets a later (or earlier!) version when they didn’t make a change—they just re-ran the same script on a different system—is in a much more confusing position. They could very well be trying to get dozens of things working at the same time; they might not be an expert on the particular part of the script that failed, and might not even realize in the moment that their Yarn or pnpm is coming from corepack. Could they (or someone else who wrote the script) have had the foresight to reconfigure corepack more deterministically? Of course, but a trap that could be avoided is still a trap; good defaults matter.

Users don’t expect that installing a deterministic version of A should give them a nondeterministic version of B. We shouldn’t force them to remember or disable an exception to this rule.

@ljharb
Copy link
Member

ljharb commented Aug 24, 2022

That’s exactly what users in this ecosystem expect - determinism, for better or worse, is simply not an expectation (beyond a lockfile), and anything else is surprising and very inconvenient, and in this case, causes many more people to have a buggy or insecure version of their package manager CLI.

@aduh95
Copy link
Contributor Author

aduh95 commented Aug 24, 2022

But good defaults matter, and I’m proposing that the default should remain the secure known-good version.

That's the catch, the known-good version is more likely to be less secure than the latest one.

I agree with Jordan, I don't think most users care about that kind of determinism (otherwise, they would set a specific version either system-wise, or at the project level). Also, as I wrote in #134, the reason Corepack is integrated in the Node.js project is that it make decouple what version of npm comes bundled with the Node.js installation, users can get a secure version of their package manager without Node.js needing to issue a security update.

@styfle
Copy link
Member

styfle commented Aug 24, 2022

I don't think most users care about that kind of determinism (otherwise, they would set a specific version either system-wise

What makes you think that users are not setting a specific version? I see many projects using npm i -g [email protected]. In fact, package managers print a warning and tell the user the upgrade command when running an outdated version.

@aduh95
Copy link
Contributor Author

aduh95 commented Aug 24, 2022

I don't think most users care about that kind of determinism (otherwise, they would set a specific version either system-wise

What makes you think that users are not setting a specific version? I see many projects using npm i -g [email protected]. In fact, package managers print a warning and tell the user the upgrade command when running an outdated version.

Having a fixed default version would not change anything for those users, since they're setting a specific one anyway, right? What I'm saying is that in my experience, folks expect to get the latest version by default, and they are used to set a specific version instead if/when they want determinism (and they should, that's why we added the packageManager field in the package.json).

@andersk
Copy link

andersk commented Aug 24, 2022

This change isn’t giving them the latest version. It’s giving them the latest version as of the time they ran corepack prepare. That could be ages earlier than the time they upgraded Node.js and corepack, since it’s a different tool they could easily forget to run regularly when upgrading everything else.

But the security concern isn’t just that they might get an older version—it’s that skipping the hash protection provided by known-good versions may make it easier for an attacker to maliciously alter the code in transit, since HTTPS can much easier to defeat in environments with intercepting proxy middleboxes and dubious certificate authorities.

@aduh95
Copy link
Contributor Author

aduh95 commented Aug 24, 2022

This change isn’t giving them the latest version. It’s giving them the latest version as of the time they ran corepack prepare. That could be ages earlier than the time they upgraded Node.js and corepack, since it’s a different tool they could easily forget to run regularly when upgrading everything else.

I reckon the current setup is not ideal, it's a first implementation and I'd be delighted to see it improve in a way that's more convenient for the users. Contributions would be much welcome, as we don't have a clear plan on how to give a way for users to update without relying on them remembering to run a command manually on a regular basis.

But the security concern isn’t just that they might get an older version—it’s that skipping the hash protection provided by known-good versions may make it easier for an attacker to maliciously alter the code in transit, since HTTPS can much easier to defeat in environments with intercepting proxy middleboxes and dubious certificate authorities.

I think #10 would be a better response to this, as it should give us the best of both worlds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dynamic default versions
7 participants