Verifying npm package integrity with CI pipeline attestations

Roundtable on software supply chain security

Intro

Most teams rely on lock files to ensure they're installing the right npm dependencies. Updates (and thus changes to these lock files) are inevitable – and this is where things have gone wrong. Code scanning tools help here, but are not enough. What if we also check that the packages we install are signed by CI before we install them?

In this article

If you’ve been following the news, you probably saw the recent phishing attack on a project maintainer’s account—it led to compromise of widely-used npm packages that were downloaded more than 1 billion times.

This gets us thinking: what if we as a community raise the bar by (1) publishing packages via CI and signing attestations of the packages we're publishing, and (2) automatically verifying attestations before installing package updates. This (and using passkeys/YubiKeys) can minimize the damage when developers inevitably get phished.

John Renner (Founding Engineer), Fraser Brown (CTO), and Deian Stefan (Chief Scientist) have spent years working on untrusted-code isolation in an academic setting, so it’s no surprise they see strong enforcement as key to making supply chain attacks harder. Recently they sat down talk through some of the ways we can make supply chain attacks harder.

Watch the full conversation here.

CI attestations for every release

At Cubist, our CI pipeline signs releases and publishes a hash and signature (“this is a Cubist release”) to Sigstore. Anyone can verify through GitHub and npm that our SDK releases come straight from our build system, as shown in the screenshot below.

GitHub provenance for CubeSigner SDK

This raises the bar for an attack. If the CI pipeline publishes attestations for every release, an attacker who steals a developer’s credentials can’t just push a malicious tarball; they’d also have to compromise the CI system itself—and doing this is harder because it requires compromising the multiple other people who maintain the CI. And if you, like us, require YubiKeys instead of TOTP as a second factor, you can eliminate whole classes of phishing vectors with relatively little effort.

Episode breakdown

Below is a minute-by-minute listening guide:

Trust & Dependencies (00:00)

  • Why developers rely on packages instead of reviewing all source code
  • Tools like Cargo Vet for auditing

Rust vs. Node (00:37)

  • Easier dependency management in Rust
  • Complexity and risks in Node’s ecosystem

Phishing & Package Security (01:34)

  • How a phishing attack led to compromised npm packages
  • Weaknesses of TOTP and the need for stronger guarantees

Secure Signing Practices (02:30)

  • Using YubiKeys in CI for a cryptographic chain of trust
  • Hardening against compromised developer machines

Key Management & Registries (04:18)

  • The challenge of registering and revoking keys
  • Opportunities for better tooling and stronger verification systems

Stronger Defaults & Enforcement (06:43)

  • Protecting keys in CI and improving 2FA
  • Safer defaults for consumers (lock files, install commands)
  • Shifting security burden from individuals to infrastructure

Transcript

If reading's more your vibe, we've got the written transcript too.

John Renner, Founding Engineer (00:00):

At the heart of the problem, you have to answer the question, do you want to read all of the source code in your project or not? And the answer of having packaged repositories and libraries and stuff is that, no, you don't want to do that. If you decide that you're not doing it, then now you're in a reputational game. You're saying, do I trust the person who published this package and do I trust that it actually came from the person that I trust? There's tools that help you read the source code like Cargo Vet, which we've used, which is pretty good. It's like onerous to update, but we do get to see all the diffs whenever we update, and we're very careful on our backend with that. I don't think everybody wants that. Maybe they should.

Deian Stefan, Co-Founder & Chief Scientist (00:37):

I mean, yeah, I guess for high security or high assurance systems that make sense to actually do this. And for Rust, it's actually been pretty reasonable to use Cargo Vet and some of the tooling that we've built to actually audit the packages that we have. For Node, it gets trickier, right? Because we're pulling in, and this is I guess, client side code, and what everybody's basically doing is you have semver and then you're pulling in packages and then those packages pull other packages. So it's not even clear that it's easy enough to audit all that code.

Fraser Brown, Co-Founder & CTO (01:06):

This to me, is really more of a story about doing enforcement on the backend. It's just much easier to manage our Rust dependencies than our TypeScript dependencies. Then I'm happy that people are setting policies on the backend because even if you submit some bogus transaction with a bogus receiver address or whatever, we should catch it.

John Renner, Founding Engineer (01:34):

Something that's a little interesting to me is that the guy that was compromised, he said he was compromised by a phishing attack and was just having a crazy morning and clicked through. What's strange to me is how npm would allow a click-through phishing attack to result in a published package. It means that there isn't a stronger necessarily cryptographic guarantee that the author is the one who created the package, presumably is using TOTP or something like that, that is phishable or repeatable that allowed them to go from, oh, we got access to your account through the web portal to we are now publishing a package. And it's interesting to me that if we were using an SSH key or any kind of key on the developer's machine, then in theory, that wouldn't have been compromised by this click-through attack.

Deian Stefan, Co-Founder & Chief Scientist (02:30):

All joking aside, I guess this is literally a thing that Fraser and I wrote about over a decade ago. If we make it easier for developers to actually sign packages when they publish them, and then you can imagine having a registry of your keys and if your key gets compromised, that goes on this basically ledger of like, "Hey, this key was owned, from that point on any package we should treat as completely compromised," but at the very least it would exactly prevent this. So I don't have your registered key, so I can't publish a package on your behalf, even if I even actually get you, even if I can phish you or even if I can actually own your machine in some sense, I would have to, if I'm using a YubiKey to sign a bunch of these software updates, then I'd have to trick you into doing that too, and it becomes a much, much harder attack to pull off.

John Renner, Founding Engineer (03:29):

It's true, but you have to think about how registering these keys with a system sort of works because on GitHub, I can register new keys with my account using any of my 2FA sources, which presumably is what happened on npm as well. I think that there is value in that. I think adding keys purely with other keys, at least in the traditional in my .ssh folder style is a little difficult of an on-ramp for people. Maybe it should be, but I think that possibly if we look at phishing-proof techniques like passkey as being a requirement for registering, then we actually have a cryptographic chain of trust. But we don't necessarily, but we have the usability you can be in your browser and you can just click around and you don't have to run a script or anything scary.

Fraser Brown, Co-Founder & CTO (04:18):

Yeah, I think actually passkeys make the entire thing we were proposing quite a bit more palatable, I think. Doesn't GitHub own npm now, is that who owns it?

Deian Stefan, Co-Founder & Chief Scientist (04:30):

Microsoft does.

Fraser Brown, Co-Founder & CTO (04:31):

Microsoft owns them, okay, good opportunity to fund the revoked key ledger here.

Deian Stefan, Co-Founder & Chief Scientist (04:39):

If I can maybe look back at what we were talking about. So I was thinking about it from, who can do what to make these kinds of problems go away or make them harder for attackers to pull off. So if I'm a developer then it seems like an easy enough thing to do is use FIDO. Ideally use a YubiKey for your second factor when it comes to things like npm, especially when it comes to things like publishing.

Fraser Brown, Co-Founder & CTO (05:11):

And try to remove weaker 2FA methods if the site you're using doesn't let you discriminate, right?

Deian Stefan, Co-Founder & Chief Scientist (05:19):

I guess if you have the infrastructure or almost like we were saying, if there is a company backing you publishing this thing, then it seems like actually signing the packages that you're publishing. And again, I think the GitHub CI pipeline is pretty good here, makes this really easy. You don't have to stand up your own Sigstore, so you can actually publish essentially an attestation of “this is me, John” publishing this package to npm, which lets people that pull packages actually verify that they're pulling things written by people they trust.

Fraser Brown, Co-Founder & CTO (05:58):

Well, written by CI that they trust.

Deian Stefan, Co-Founder & Chief Scientist (06:01):

Exactly. Right, right, right. Yeah, exactly.

Fraser Brown, Co-Founder & CTO (06:02):

It's a higher bar: dude that you trust. It's like dude's machine gets compromised, CI that you trust. That means probably multiple people’s machines got compromised.

Deian Stefan, Co-Founder & Chief Scientist (06:11):

So what we do, right, with a bunch of our software releases is, we actually sign them and we publish them on Sigstore. So Sigstore kind of does a similar thing to what we're doing. And GitHub lets you, has built-in support for this now. So anytime you do a release, it actually publishes the hash and a signature saying, “Hey, this is my new release.” And npm actually does pick this up. So for our releases, we do this with our SDK, you can verify that these were actually coming from our build pipeline. So GitHub actually has a nice integration here.

John Renner, Founding Engineer (06:43):

I think there's sort of this radial story here, which is if you're the developer building a package that's going to be distributed, you need to protect your keys somehow. Storing them purely in CI can be a very good way of doing this. If you're generating signatures or even if you're deploying to npm straight from CI, keeping that off your machine is probably good. The next step is, okay, well how can I protect the ability to register new keys? And we talked about how FIDO is really good at that and how your package manager needs to kind of work with you here. npm needs to have a better security story here to prevent this kind of thing. You can maybe work with them today and remove your second factors that aren't as good, but you want to use strong factors here. And then sort of the next step is, “I am a developer consuming a package. How can I protect myself?” And there it's like be diligent about your lock files. Try not to use versions of install commands that update randomly. And this is something that I would love to see npm also change the defaults a bit on to make them more secure by default. But if today, learn the right scripts and try to use the right ones.

Fraser Brown, Co-Founder & CTO (07:51):

And I would say also specifically for consumer facing stuff, so not attacks that are targeting developers, the more enforcement, like security enforcement, you can do not in Node world, I think the better.

About

Blog & Updates

Explore Related Blog Posts