Let's document how to verify a Node.js downloads on the website
As discussed in https://github.com/nodejs/node/issues/58904#issuecomment-3031456396, the way we document how to verify Node.js downloads is not ideal, and there seems to be consensus for switching our recommendation from the public OpenPGP.org server to our own nodejs/release-keys repository. On top of changes in the nodejs/node README, we should also host on the website what is the trusted way to verify a Node.js download.
What we need to provide on the website (presumably on the Downloads page) would be:
- a git commit hash to a revision of nodejs/release-keys that contain keys to all.
- a SHA-256 of the
gpg-only-active-keys/pubring.kbxon that revision.
Opening this now in case it involves design changes, but it shouldn't land until after the nodejs/node README is edited (currently it still points to keys.openpgp.org as the recommended source).
Adding the commit hash would make the most sense to ensure that it's added to https://nodejs.org/dist/index.json.
My existing automation looks a bit like this for finding the right files for the version I need. Extending this to also fetch the commit hash of what keys to take would make sense, and then from there I can make SHA256's of that which I store in an array to act as TOFU pins for the keys.
# Check for the latest NodeJS 22 version
LATEST_NODE=$(curl --tlsv1.3 -s https://nodejs.org/dist/index.json | jq -re '.[] | select(.version | startswith("v22")) | .version' | head -n 1)
# make and cd dir in tmp
mkdir /tmp/node-install/
cd /tmp/node-install/
# Get SHA256s, signature and binaries for the desired version
curl -O --tlsv1.3 -s --output /tmp/node-install/SHASUMS256.txt https://nodejs.org/dist/$LATEST_NODE/SHASUMS256.txt
curl -O --tlsv1.3 -s --output /tmp/node-install/SHASUMS256.txt.sig https://nodejs.org/dist/$LATEST_NODE/SHASUMS256.txt.sig
curl -O --tlsv1.3 -s --output /tmp/node-install/node-$LATEST_NODE-linux-x64.tar.xz https://nodejs.org/dist/$LATEST_NODE/node-$LATEST_NODE-linux-x64.tar.xz
# Check SHA256 of the binaries tar, this is done BEFORE gpg checks because it is far less attack surface than GPG
grep node-v*-linux-x64.tar.xz SHASUMS256.txt | sha256sum -c -
if [ $? -ne 0 ]; then
echo "File does not match pinned SHA256 hash. Potential supply chain attack detected on NodeJS CDN."
exit 1
fi
Let me try to rephrase to make sure I understand the proposal. You'd like to see an object such as:
{
"version": "v24.3.0",
"date": "2025-06-24",
"nodejs/release-keys": {
"commit": "3ead0a2d07b13e469fd97ef39facf6d31993fb71",
"sha256": "4f8664db3ba7311589efe3697881155f8ba4bb5d36fe84bdfa7e77359408b1bf"
},
…
}
Which you could use in your script such as:
set -ex
set -o pipefail
LATEST_NODE=$(curl -s https://nodejs.org/dist/index.json | jq -re 'first(select(.lts))')
PUBRING=$(mktemp)
curl -fsLo "$PUBRING" "https://github.com/nodejs/release-keys/raw/$(echo "$LATEST_NODE" | jq -re '.["nodejs/release-keys"].commit')/gpg-only-active-keys/pubring.kbx"
shasum -a 256 "$PUBRING" | grep -q "$(echo "$LATEST_NODE" | jq -re '.["nodejs/release-keys"].sha256')"
VERSION=$(echo "$LATEST_NODE" | jq -re '.version')
TAR="node-$VERSION-linux-x64.tar.xz"
curl -fso "$TAR" "https://nodejs.org/dist/$VERSION/$TAR"
curl -fs "https://nodejs.org/dist/${VERSION}/SHASUMS256.txt.asc" \
| gpgv --keyring="${PUBRING}" --output - \
| grep -E "$TAR" \
| shasum -c -
rm "$PUBRING"
I wonder if an issue with this approach is that it'd encourage folks to check keys dynamically, but their setup would be much more robust if they have hard-coded the hash 🤔
Yeah this seems pretty much what I am on about. The hash of the keyring would be subject to change though within the constraint of a major release as maintainers come/go and keys are rotated, so for that reason I would fetch it dynamically personally and rely on a hardcoded SHA256 of the keyring in my script and use that as a TOFU pin of the keyring. The downside is that it's a potential annoying thing to deal with, but it's worth the extra assurance IMHO.
<------------------------------ my own comments on the signing process as-is ------------------------------>
Ideally this signing process would be refactored to be much more robust, and a build comparison/attestation + shamir secret sharing approach would take place. This way you could have a dedicated signing key for each major release, and in order to sign anything, at least X out of Y maintainers have to approve a signature of a particular SHA256 derived from either the binaries produced or from the source tree as a whole for git tags.
This way maintainers would have the copy of the source tree locally to sign and then also build the binaries locally in order to reproduce what the build system produces, in order to both distrust the build system and other maintainers, eliminating a single maintainer going rogue potentially and doing evil signatures, or a compromised build machine anywhere. It also has the benefit of having a very clear decentralised system for making releases without leaving private keys on a build server somewhere and waiting for it to be stolen. You would have to pwn and/or place multiple maintainers under duress instead of just one to do some damage in this scenario.
Sharing a secret does not decrease the risk of it leaking, quite the contrary – and in the hypothesis that there is a rogue releaser, having a shared secret would actually help them cover their traces. The plan is not really realistic anyway as we're building on platforms for which we have close to 0 contributors let alone releasers (e.g. Windows).
We're getting far off-track, this issue is meant to discuss about changes to the download page. Changes to the index.json file should be directed at https://github.com/nodejs/nodejs-dist-indexer, and changes to the release process to https://github.com/nodejs/Release. We can continue the discussion there if you want to champion a proposal, I'll be hiding our comments since they are off topic.
So something as critical as this to the entire platform security chain, you are just going to mark as off topic and not even open a new thread. Feel free to mark this off topic too, but just keep in mind if you don't follow these comments up you really should document the current shortcomings that put all of us nodejs users at risk. Ouch !!!
@aduh95
there seems to be consensus for switching our recommendation from the public OpenPGP.org server to our own nodejs/release-keys repository
Could you please point to where you have gained consensus for this? I would expect to see an approved PR on https://github.com/nodejs/node/pulls with changes to https://github.com/nodejs/node/blob/main/README.md#verifying-binaries before claiming consensus. Perhaps I have overlooked something here?
https://github.com/nodejs/docker-node is currently and successfully using hkps://keys.openpgp.org with a fallback to keyserver.ubuntu.com to verify and install Node.js. See for instance https://github.com/nodejs/docker-node/blob/main/Dockerfile-debian.template
# use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150
&& export GNUPGHOME="$(mktemp -d)" \
# gpg keys listed at https://github.com/nodejs/node#release-keys
&& set -ex \
&& for key in \
"${NODE_KEYS[@]}"
; do \
{ gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \
{ gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \
done \
&& curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \
&& curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& gpgconf --kill all \
&& rm -rf "$GNUPGHOME" \
&& grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
&& rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
If there is some reason why keyserver.ubuntu.com can no longer be used as a fallback, then perhaps you could explain?
I wonder also how Google manages to have one key for everything (see https://www.google.de/linuxrepositories/) whereas Node.js needs individual keys with a high maintenance overhead impacting downstream Docker building repos like https://github.com/nodejs/docker-node and https://github.com/cypress-io/cypress-docker-images with a dependence on individual Node.js releasers.
If there is some reason why keyserver.ubuntu.com can no longer be used as a fallback, then perhaps you could explain?
Recommending using nodejs/release-keys is not at all the same thing as recommending against using keyserver.ubuntu.com as a fallback.
I would expect to see an approved PR […] before claiming consensus
Well do you have any concerns with the suggested recommendation?
I've marked my previous post as off-topic, as it concerns details of how to verify Node.js downloads. Instead I note that the https://nodejs.org/en/download page already links to the https://github.com/nodejs/node#verifying-binaries section and to avoid repeating information, I suggest to leave it that way without making changes to the website
the https://nodejs.org/en/download page already links to the https://github.com/nodejs/node#verifying-binaries section and to avoid repeating information, I suggest to leave it that way without making changes to the website
The idea behind having the information on the website is that if, for whatever reason, you cannot (or don't want to) access and/or trust github.com, having the information on the website provides an alternative. Repeating the information does indeed come with downsides (more maintenance burden to keep it up-to-date), but also with upsides (mainly the info is more broadly available). There's a tradeoff to be made, and maybe the current link is the "right" tradeoff, or maybe not, I don't claim consensus on that point to be clear.
Antoine can we just fetch (on SSR) GH raw and display it with our style ?
@aduh95
There's a tradeoff to be made, and maybe the current link is the "right" tradeoff, or maybe not, I don't claim consensus on that point to be clear.
I would tend to keep it simple and retain just the current link https://github.com/nodejs/node#verifying-binaries where the content has just been updated. The keyrings are located on GitHub (https://github.com/nodejs/release-keys), so there is a reliance there in any case.
We are discussing it again in https://github.com/nodejs/node/pull/60490 and at least @aduh95 and I agree that this should not be in the Node.js README because it clutters the README with too many details that should be in a dedicated page. I suggest that we just move that information to the website and let the README link the website instead.