Upgrade transitive dependencies with PNPM: Fix the security vulnerabilities without breaking things
Fixing security vulnerabilities may be a frustrating task, especially when it involves transitive dependencies. Learn how to upgrade them without affecting your direct dependencies.
Nowadays, security vulnerabilities are a common issue in software development. Luckily, we have tools like GitHub Dependabot to help us keep our dependencies up-to-date with automated detection and pull requests.
However, it doesn't always work as expected. Since some dependencies are transitive, upgrading them may be a diffult task for these tools since they don't know the impact of the changes and what decision to make when there are conflicts. We need to handle these cases manually.
Methods that do not work
- Official commands like
pnpm up
will mess up yourpnpm-lock.yaml
file. There's an issue about this which is still open at the time of writing. - Install the latest version of the direct dependency that has the target transitive dependency may not work. If the direct dependency didn't upgrade the version definitions, the transitive dependency will not be upgraded since it has been resolved and locked in the
pnpm-lock.yaml
file.
The solution
The overrides
field is a powerful feature in PNPM that allows you to override some version resolutions. We'll use this feature to upgrade transitive dependencies and make the change as minimal as possible.
Let's use the above Dependabot alert as an example. It tells us that the follow-redirects
package has a security vulnerability, and the patched version is 1.15.6
. However, the direct dependency gatsby
uses axios
which depends on [email protected]
.
Step 1: Find the transitive dependency
There are many ways to locate the transitive dependency, but I would recommend the most straightforward way: search the pnpm-lock.yaml
file.
How about "pnpm why"?
The pnpm why <package>
command is useful indeed. However, it may confuse you in this case. For example, when I run pnpm why follow-redirects
, here's a part of the output:
Actually, there's only one resolution for follow-redirects
in the pnpm-lock.yaml
file. The pnpm why
command may show you multiple paths that depend on the same version of the package.
Step 2: Add the overrides
The eaiest case is that only one resolution exists in the pnpm-lock.yaml
file. You can add the override directly to the package.json
file:
If you are in a workspace, you should add the override to the workspace's root package.json
file.
Step 3: Apply the changes
Run pnpm install
to apply the changes. We can see the follow-redirects
package is upgraded to 1.15.6
in the pnpm-lock.yaml
file with minimal changes.
Now you can remove the overrides
field from the package.json
file to keep it clean. Then run pnpm install
again to validate the changes can be applied without the overrides
field.
Troubleshooting
The above steps are the ideal case. Things may not always go as expected. Here are some tips for troubleshooting:
The version is reverted after removing the "overrides" field
This may happen when the dependent package has a fixed version or a range that doesn't include the target version. Check the package.json
file of the dependent package, in this case, axios
, to see if this applies.
If so, you need to keep the overrides
field in the package.json
file until the dependent package upgrades the version definitions.
There are multiple resolutions for the transitive dependency
As the project grows, the pnpm-lock.yaml
file may float with multiple resolutions for the same package. For instance, there may be two major versions of the same package foo
:
The vulnerability report shows that [email protected]
has a security issue which is fixed in 1.0.1
, and [email protected]
is not affected. We could not simply add an override to foo
:
- If we add an override
"foo": "^1.0.1"
, the[email protected]
will be downgraded to1.0.1
. This may break the project since the[email protected]
may have some new features that are used in the dependent packages. - If we add an override
"foo": "^2.0.0"
, the[email protected]
will be upgraded to2.0.0
. This may break the project since the[email protected]
may have some breaking changes.
Assuming foo
follows Semantic Versioning, we can add an override like this:
This will only upgrade the [email protected]
to 1.0.1
and keep the [email protected]
unchanged.
Conclusion
Before PNPM supports upgrading transitive dependencies directly, the overrides
field is a good workaround to fix security vulnerabilities without breaking things. I hope this article helps you to handle these cases more efficiently. In Logto, we use this method to keep our dependencies up-to-date and secure.