Open Letter: Let's stop supply chain attack
With this letter, I’m proposing some modification of the publish process of all package managers, and Github because it’s the most trusted service provider in the open-source world.
Recently, one of the widely used npm packages is compromised and malicious code was distributed using it. This could happen because
-
configuring 2FA nearly blocks automation (and thus provenance)
-
a user with token can publish a new version from local machine
Currently using 2FA for publishing is nearly impossible, and deploying manually each time to use 2FA will increase the risk of mistake or leak. We have to alter the situation, and our approach to the problem. We can do 2FA and we should. But how? I said configuring 2FA almost blocks automation.
But there’s a solution. The basic idea is to require 2FA during deployment using different device than the CI machine. We have all the necessary technologies to do this. Web Push and WebAuthn, combined with some constraints for the package registries can completely prevent supply chain attack. Package managers should
-
enforce different-device 2FA
-
and make it the only way to deploy a package
Traditional 2FA for publishing means that we need to carry the 2FA code to the CI machine, but we don’t have to do it if we can send a request to maintainers’ devices as a step of publish process. Web Push will do the trick here. And we have WebAuthn. It can replace number-based 2FA with a better mean.
Before diving into the idea, there are two flavor of this idea.
- The first one is involving a third-party service and sharing the verification service among package registries for various languages.
This requires a trust from package registry to the verification server. I think it would be ideal if a trusted service provider like Github can run a service like this. Everyone using an open-source is already trusting Github, and that’s why.
As a side note, I tried to implement it as a third-party service by myself in the past, but I concluded I can’t build an enough amount of trust for this kinds of services.
- The second one is package registry running the verification server by themselves.
I’ll describe how does it work using the second one.
Let’s look at the flow.
Once the CI publishes a package, the package registries asks the verification service for verification. The verification server sends a push notification to maintainers, and when they click the notification, they go to the page where they can approve a deployment using 2FA methods like WebAuthn. While the process, the package version in the package registry goes into verifying state. The versions in this state should not be downloadable from the end-user side, because it potentially contains malicious code. After the approval from the maintainer, the package goes to downloadable, public state.
With this technique, we can go a bit further to prevent more attacks. We can introduce the concept of multiple-approvals. We can create multiple approver system where all of them should approve the deployment to make it public while making an approver a group so that it does not delay legit deployment too much.
Let’s look at the example configuration for SWC. This is not real, but it demonstrates the concept of approve group well.
This is how I want to configure publish permission of SWC if there’s a such system. This graph means all packages in the @swc/
namespace must be approved by at least three people before publicly visible.
-
Me (
@kdy1
) -
One of the core team members, excluding me
-
One of my teammates at Vercel, excluding me
I never leave my devices unlocked under any circumstances, but well, there are still enormous amounts of attack vectors and my device may get compromised, and in this case, the attacker will have access to one of my devices and the NPM_TOKEN
. But with this approval system there’s no way to distribute malicious codes using my token and device - they still need to do much more work to distribute them.
Of course, this slows down the deployment process. But you have to think about this: merging PR without reviews is much faster, but most projects merge PRs after reviewing. It’s same. There’s a reason for PR reviews, and it’s much more important for public deployments. It gives maintainers a trump card to stop a virus distribution at the last second.
Open-source implementation
-
Live Demo: https://tpp.dudy.dev/app
-
Repository: https://github.com/dudykr/tpp
TPP stands for Trustable Package Publication. It does not implement advanced topics like requesting specific device for approval (because it’s a demo), but it demonstrates the concept.
Device registration
-
You register device for push notification.
-
Upon registration, the app requests bio auth or FIDO using WebAuthn.
Package creation
-
You create a test package with an arbitrary name.
-
The default approval group (with name
(Default)
) is generated, and you are added to the group. -
You may add invite your coworkers to your packages, if you want to create multi-approval publish process.
Mimicing publish process
-
Press
Demo: Start publishing your package
. -
Receive a push notification.
-
Click it. It will bring you to
https://tpp.dudy.dev/packages/${packageId}/requests/${requestId}
Approving requests
- Press
Approve Request
-
Touch
Touch ID
(if you are on macbook) or use mobile device. -
You are done! If you added another approval group, they should approve, too.
-
Once all approval groups approve, the approval request is marked as
approved
, meaning the version of the package is good to go public.
Wrapping up
Of course, this is an extra work for many people. It’s especially true for package registry developers, and this will request bio auth every time a package is published. But I’m sure that the gain worths the effort. If you configure three or more independant approval groups, even you can prevent nearly all kinds of attacks, even rubber hose thing. Adding some steps for security is sometimes inevitable, but theoretically, if we can secure the TPP server, this is the last thing we have to do to eliminate supply chain attacks from the planet.