I have not been able to find a single page that actually explains how npm’s ECDSA signing system works. The closest I could find is the official documentation, but as far as I can tell from that documentation, this system is completely useless: a malicious source can simply replace both the signing key and signed data, so that the malicious data will still have valid signatures. And there don’t seem to be any measures in npm to detect malicious key substitution.

This question is possibly related to How do atom's apm and npm verify package integrity?, but this question is specifically about the ECDSA signing system. At least the deprecated PGP signing system has a well-documented, independent, mechanism for distributing and verifying the signing key.


1 Answer 1


NPM's signatures allow you to check that when you have a copy of the package, that it has the same contents as the package in the NPM registry. Thus, you can prove that a downloaded copy was not tampered with later. Signing packages is important in particular when mirrors or proxies are involved, and is done similarly by other package management systems.

This does not prove that it's the same contents as were uploaded by the package author, i.e. this signature does not defend against a malicious registry.

You correctly point out that the security of this approach hinges on knowing which keys are valid.

  • For PGP signatures, NPM has published its key on Keybase
  • For ECDSA signatures, NPM publishes its keys through the NPM API

For example, the NPM documentation lists light-cycle as an example of a package with a signature. Its metadata contains:

"dist": {
  "integrity": "sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ==",
  "shasum": "c305f0113d81d880f846d84f80c7f3237f197bab",
  "tarball": "https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz",
  "fileCount": 11,
  "unpackedSize": 25612,
  "npm-signature": "...",
  "signatures": [
      "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
      "sig": "MEUCIQCX/49atNeSDYZP8betYWEqB0G8zZnIyB7ibC7nRNyMiQIgHosOKHhVTVNBI/6iUNSpDokOc44zsZ7TfybMKj8YdfY="

(I removed the PGP signature since it is very long.)

Using https://registry.npmjs.org/-/npm/v1/keys, we can fetch NPM's keys:

  "keys": [
      "expires": null,
      "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
      "keytype": "ecdsa-sha2-nistp256",
      "scheme": "ecdsa-sha2-nistp256",
      "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg=="

We can see that the keyid in the signature matches the ID of one of NPM's keys, so that we could proceed to verify the signature.

  • As I said in the question, this is only useful if the key comes from a different source (or the same source, but without using the mirror/proxy) to the package. But then how does npm know where to get the key from? Aug 9, 2022 at 15:41
  • @BrianDrake Any NPM-compatible registry offers its keys via the /-/npm/v1/keys endpoint. So the NPM client can download the public keys from that endpoint when trying to verify the integrity of a package that belongs to that registry. Of course, this would have to bypass any mirrors or caches and contact the original registry. I consider that to be equally secure to publishing the key via Keybase (pro: no third party is involved, con: compromise of the registry can change both packages and keys, but that con is not part of the threadmodel anyway).
    – amon
    Aug 9, 2022 at 16:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .