Skip to content

Conversation

@zkochan
Copy link
Member

@zkochan zkochan commented Sep 9, 2025

close #9921

@zkochan zkochan marked this pull request as ready for review September 10, 2025 14:14
@zkochan zkochan requested review from a team and Copilot September 10, 2025 23:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new minimumReleaseAge feature that delays the installation of newly released dependencies to reduce security risks from compromised packages. The feature ensures only packages published before a specified time cutoff are installed, with an optional exclude list for trusted dependencies.

  • Adds minimumReleaseAge configuration option that specifies delay in minutes before new versions can be installed
  • Implements minimumReleaseAgeExclude to exempt specific packages from the release age restriction
  • Integrates time-based filtering throughout the dependency resolution pipeline with strict enforcement option

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
store/store-connection-manager/src/createNewStoreController.ts Adds minimumReleaseAge to controller options and enables strict published-by checking
resolving/npm-resolver/test/distTagsByDate.test.ts New test file for dist-tag filtering based on publish date cutoffs
resolving/npm-resolver/src/pickPackageFromMeta.ts Major refactoring to move date filtering logic and add dist-tag repopulation
resolving/npm-resolver/src/pickPackage.ts Adds strict mode for published-by checks with error handling
resolving/npm-resolver/src/index.ts Adds strictPublishedByCheck option to resolver factory
pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts Calculates maximum publish date from minimumReleaseAge configuration
pkg-manager/resolve-dependencies/src/resolveDependencies.ts Applies release age filtering during dependency resolution
pkg-manager/plugin-commands-installation/test/add.ts Test for installation failure when no versions meet release age requirement
pkg-manager/core/test/install/minimumReleaseAge.ts Core functionality tests for minimumReleaseAge feature
pkg-manager/core/src/install/index.ts Passes minimumReleaseAge options to dependency resolution
pkg-manager/core/src/install/extendInstallOptions.ts Type definitions for new minimumReleaseAge options
config/config/src/types.ts Configuration type definitions for release age settings
config/config/src/Config.ts Interface definitions for minimumReleaseAge configuration
.changeset/full-dolls-invite.md Documentation of the new feature and usage examples

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@zkochan zkochan requested a review from Copilot September 11, 2025 14:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@limonte
Copy link

limonte commented Sep 16, 2025

How exactly does this parameter work?

Let's say I have minimum-release-age set to 3 days:

minimum-release-age=4320

Then I run:

pnpm install my-package

and my-package has these versions:

  • 6.0.0 released 10 days ago
  • 6.6.6 released 4 days ago - this version contains critical vulnerability and it has been reported to GitHub Advisory Database
  • 6.6.7 released 2 day ago - fixes the vulnerability from v6.6.6

What version would be installed?

arcanis added a commit to yarnpkg/berry that referenced this pull request Sep 18, 2025
…ig options (#6901)

## What's the problem this PR addresses?

closes #6899. See rationale in pnpm/pnpm#9921
and pnpm/pnpm#9957, but the tl;dr is that with
the recent uptick in compromised npm packages, this can offer some level
of protection to prevent end-users from installing malware prior to
detection and removal from registries.

There are a few differences from the pnpm implementation. I felt these
differences made sense with some of the other features `yarn` supports,
but I also understand the desire for parity between package managers, so
open to thoughts there.

1. `npm` added in the option names (`npmMinimumReleaseAge` versus
`minimumReleaseAge`). Since yarn implements many resolvers and this is
only implemented in the case of the npm resolver, I felt that this made
the behavior of the options more clear.
2. `npmMinimumReleaseAgeExclude` supports not only package names like
pnpm's implementation, but it also supports:
- exact match on package locators (i.e. exact package resolutions --
like `@aws-sdk/[email protected]` or `@aws-sdk/types@npm:3.877.0`)
- micromatch glob patterns on package descriptors (i.e. semver
descriptors -- like `@aws-sdk/types@^3.0.0`,
`@aws-sdk/types@npm:^3.0.0`, `@aws-sdk/types@*` or `@aws-sdk/*`)

The rationale here is mostly in the case that you know certain package
versions that are affected (e.g. `[email protected]`) or if you need to
upgrade to an excluded version, but its part of a monorepo -- that's
where the micromatch glob comes in handy.

## How did you fix it?

I added the options and checked for them within the NPM semver resolver
when evaluating candidates. I'm new to this codebase -- I think I've
made all the updates needed and was able to test some scenarios
successfully (see below).

### Testing manually

<spoiler>

<details>
I used `@aws-sdk/[email protected]` to test. At the time of writing, this
package was published 7742 minutes ago, which is less than 10000 minutes
(used in configurations below).

#### `yarn add @aws-sdk/[email protected]` fails ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
```

install command
```bash
❯ yarn add @aws-sdk/[email protected]
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0082: │ @aws-sdk/types@npm:3.887.0: No candidates found
➤ YN0000: └ Completed
➤ YN0000: · Failed with errors in 0s 11ms
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves prior version ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
```

install command
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.862.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 237ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 271ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.862.0 (via npm:^3.0.0)
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves most recent version with
package exclude = `@aws-sdk/types` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/types"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.887.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 237ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 265ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.887.0 (via npm:^3.0.0)
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves most recent version with
package exclude = `@aws-sdk/*` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/*"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.887.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 205ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 232ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.887.0 (via npm:^3.0.0)
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves most recent version with
package exclude = `@aws-sdk/[email protected]` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/[email protected]"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.887.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 292ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 324ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.887.0 (via npm:^3.0.0)
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves most recent version with
package exclude = `@aws-sdk/types@^3.0.0` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/types@^3.0.0"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.887.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 248ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 278ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.887.0 (via npm:^3.0.0)
```

#### `yarn add @aws-sdk/types@^3.5.0` resolves prior version with
package exclude = `@aws-sdk/types@^3.0.0` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/types@^3.0.0"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.5.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.862.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 219ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 256ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.862.0 (via npm:^3.5.0)
```


#### `yarn add @aws-sdk/types@^3.5.0` resolves prior version with
package exclude = `@aws-sdk/types@npm:^3.0.0` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/types@npm:^3.0.0"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.5.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.862.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 223ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.862.0 (via npm:^3.5.0)
```

#### `yarn add @aws-sdk/types@^3.0.0` resolves most recent version with
package exclude = `@aws-sdk/types@npm:^3.0.0` ✅

`.yarnrc.yml`

```yaml
npmMinimumReleaseAge: 10000
npmMinimumReleaseAgeExclude:
  - "@aws-sdk/types@npm:^3.0.0"
```

install command - installs `@aws-sdk/[email protected]` ✅ 
```bash
❯ yarn add @aws-sdk/types@^3.0.0
➤ YN0000: · Yarn 4.9.4-git.20250917.hash-a05df867e
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @aws-sdk/types@npm:3.887.0, @smithy/types@npm:4.5.0, tslib@npm:2.8.1
➤ YN0000: └ Completed in 0s 239ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 269ms
❯ yarn why @aws-sdk/types
└─ test-yarn-project@workspace:.
   └─ @aws-sdk/types@npm:3.887.0 (via npm:^3.0.0)
```
</details>

</spoiler>


## Checklist

<!--- Don't worry if you miss something, chores are automatically
tested. -->
<!--- This checklist exists to help you remember doing the chores when
you submit a PR. -->
<!--- Put an `x` in all the boxes that apply. -->
- [x] I have read the [Contributing
Guide](https://yarnpkg.com/advanced/contributing).

<!-- See
https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released
for more details. -->
<!-- Check with `yarn version check` and fix with `yarn version check
-i` -->
- [x] I have set the packages that need to be released for my changes to
be effective.

<!-- The "Testing chores" workflow validates that your PR follows our
guidelines. -->
<!-- If it doesn't pass, click on it to see details as to what your PR
might be missing. -->
- [x] I will check that all automated PR checks pass before the PR gets
reviewed.

---------

Co-authored-by: Maël Nison <[email protected]>
@karlhorky
Copy link

karlhorky commented Oct 18, 2025

What version would be installed?

@limonte I believe the answer is [email protected] (including the critical vulnerability), because minimumReleaseAge is not connected to vulnerability reports.

However, if the user notices the vulnerability via pnpm audit or Dependabot, they can allow installation of the exact version [email protected] via minimumReleaseAgeExclude:

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.

Add a way to enforce a minimum package age policy

4 participants