Skip to content

feat(runtime): verify Node.js downloads with PGP signatures#1848

Open
fengmk2 wants to merge 24 commits into
mainfrom
feat/verify-node-shasums-pgp
Open

feat(runtime): verify Node.js downloads with PGP signatures#1848
fengmk2 wants to merge 24 commits into
mainfrom
feat/verify-node-shasums-pgp

Conversation

@fengmk2

@fengmk2 fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member

When vp installs a managed Node.js version, it now verifies the release's PGP signature (the clearsigned SHASUMS256.txt.asc) against Node's release keys before trusting any checksum. Previously the plain SHASUMS256.txt was trusted as-is, so anyone able to tamper with it could swap in a matching malicious archive.

This is the same guarantee as gpgv against the official keyring, built in (pure Rust, no gpg required). The keyring is vendored from nodejs/release-keys and kept current by a weekly workflow.

Example output

A normal install is unchanged (verification is transparent):

$ vp env install 20.18.0
Installing Node.js v20.18.0...
Installed Node.js v20.18.0

A tampered or untrusted SHASUMS is rejected before anything is installed:

$ vp env install 20.18.0
Installing Node.js v20.18.0...
error: Failed to download Node.js runtime: PGP signature verification failed for node-v20.18.0-darwin-arm64.tar.gz: signature does not match a trusted Node.js release key

The official nodejs.org source always requires a valid signature. The unofficial musl builds, and custom mirrors that publish only SHASUMS256.txt, have no signature to verify, so they fall back to checksum-only verification. That is an expected, documented condition for those sources, so it is recorded in the debug log (VITE_LOG) rather than warning on every install.

Binary size

The .node NAPI binding (vite-plus-cli) is unaffected: no size change. pgp and the vendored keyring are reachable only through vite_js_runtime, which is linked into the global vp binary, not into the binding (vite_install / vite_pm_cli). Only the global vp binary grows, by ~1.2 MiB (rPGP plus the RustCrypto verification code and the embedded release keyring).

Closes #1807

…Node

Download the clearsigned SHASUMS256.txt.asc and verify it against the
embedded Node.js release keys before trusting any checksum, so a tampered
SHASUMS file cannot pass off a malicious archive whose hash it controls.
Falls back to the plain SHASUMS256.txt for musl unofficial builds, which
publish no signature.

Closes #1807
@fengmk2 fengmk2 self-assigned this Jun 15, 2026
@netlify

netlify Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview ready!

Name Link
🔨 Latest commit 0776757
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a32930de54a340008221a83
😎 Deploy Preview https://deploy-preview-1848--viteplus-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@socket-security

socket-security Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​pgp@​0.19.010010010010070

View full report

fengmk2 added 2 commits June 15, 2026 22:50
The base64 armor in the vendored Node release keys trips crate-ci/typos;
exclude the .asc assets and fix the real "unparseable" typo in code.
- Scope the ShasumsSignature import to non-musl so musl builds don't emit
  an unused-import warning (the symbol is only referenced under the same cfg).
- Return String from verify_signed_shasums instead of Str so the caller can
  borrow it directly for parse_shasums, dropping two needless allocations and
  simplifying the error mapping to map_err.
@fengmk2

fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0a3fad1dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vite_js_runtime/src/providers/node.rs
…ature

Requiring SHASUMS256.txt.asc on every non-musl source regressed
VP_NODE_DIST_MIRROR users whose mirror ships only the archives plus the
plain SHASUMS256.txt. Make signature verification mandatory only for the
official nodejs.org source; for custom mirrors it is best-effort, falling
back to the plain SHASUMS when the .asc is unavailable. A downloaded but
invalid signature still fails everywhere.
@fengmk2

fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48df9085b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vite_js_runtime/src/pgp_verify.rs
Comment thread crates/vite_js_runtime/src/providers/node.rs Outdated
Address two Codex review findings:

- pgp_verify: a raw cryptographic match no longer suffices. Reject revoked
  signing keys, and require the signature's creation time to fall within the
  signing key/subkey validity window. This mirrors gpgv and stops a compromised,
  long-expired release key from signing a fresh SHASUMS for a current release,
  while genuine old signatures made when the key was valid still verify.
- node: base whether the signature is required on the resolved host
  (nodejs.org) rather than merely whether VP_NODE_DIST_MIRROR is set, so a
  mirror pointed back at the official host still requires verification.
@fengmk2

fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ed93d29e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vite_js_runtime/src/pgp_verify.rs Outdated
Comment thread crates/vite_js_runtime/src/pgp_verify.rs Outdated
Address two Codex review findings on the PGP key-policy checks:

- Subkey signing path now rejects non-signing or revoked subkeys: a matched
  subkey must carry a signing-capable binding signature (with a valid embedded
  primary-key back-signature) and must not be revoked, so a leaked
  encryption-only or revoked subkey secret can no longer pass the release-
  signing check.
- Expiry is now evaluated against the self/binding signature effective at the
  release signature's creation time, not the loosest expiration across all
  self-signatures, matching gpgv when a key's expiry changes over time.

Adds a regression fixture (v18.14.0, signed by a since-expired key) proving a
genuine old signature still verifies.
@fengmk2

fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c967de5572

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/vite_js_runtime/src/pgp_verify.rs Outdated
Comment thread crates/vite_js_runtime/src/pgp_verify.rs Outdated
Comment thread crates/vite_js_runtime/src/pgp_verify.rs
Address Codex review findings:

- Primary-key path now only lets the key's own self-issued certifications and
  direct signatures define its policy (third-party certifications are ignored),
  and rejects a primary marked certify-only, matching the rigor of the subkey
  path.
- Document the two intentional limitations of the gpgv-style curated-keyring
  model: expiry is checked against the signature's self-asserted creation time
  (so it is defense-in-depth, not protection against a leaked key that backdates
  a forgery; revocation plus the curated keyring is the real boundary), and the
  vendored keyring is a snapshot that must be refreshed as Node's releaser set
  changes (a brand-new releaser's release fails closed until then).
@fengmk2

fengmk2 commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: d53128abe7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fengmk2 added 7 commits June 16, 2026 09:01
The Round-3 change selected the self-signature "effective at the release
signature time" (created on or before it), but a release key is often
re-certified later (e.g. to extend its expiry). For such keys the only
self-signature in the vendored keyring postdates older release signatures, so
no self-signature was found and verification rejected legitimate releases
(e.g. node-v20.18.0, whose signing key was re-certified after the release).

Use the current (latest) self/binding signature to determine the effective
expiry, as gpgv does. Adds a regression fixture for the re-certified-key case.
Enforcing key expiry diverged from the reference (gpgv) and rejected
legitimate Node releases: gpgv reports such signatures good (exit 0) even
when made after the signing key's expiry (e.g. node-v16.20.0, signed days
after its key expired), and when the key was re-certified after the release
(node-v20.18.0). The earlier expiry checks broke `vp env exec`/install and the
CLI snap tests for those versions.

Verification now mirrors gpgv against the curated release keyring: accept a
cryptographically valid signature from a trusted, non-revoked key; a subkey
signature additionally requires a signing-capable, validly-bound subkey so a
leaked encryption-only subkey cannot sign. Expiry is not enforced (it is
advisory in gpgv and is defeatable by backdating anyway); revocation and the
curated keyring remain the trust boundary. Adds regression fixtures for the
expired-at-signing and re-certified-key releases.
Document the design, gpgv-matching verification policy, mirror handling, and
the intentional trust-model limitations (expiry not enforced, vendored keyring
currency, rsa advisory).
Add a weekly workflow (update-node-release-keys.yml) that regenerates the
embedded Node.js release keyring from nodejs/release-keys via a reproducible
script and opens a PR for human review. The PR body includes a before/after
diff of added/removed/modified keys (by fingerprint and user id). The keyring
is never auto-merged, since it is the trust anchor for SHASUMS verification.

Also relax the block-count test to be self-consistent with the vendored file
(keeping a >=28 floor) so adding keys upstream needs no test edit, document the
automation in the RFC, and allow the "fpr" gpg colon-record term in typos.
- update-node-release-keys.sh: decode gpg \xHH-escaped uids in the PR body
  (so e.g. "http\x3a//" renders as "http://"), clean up the temporary gpg
  homedirs instead of leaking them, and normalize CRLF/CR to LF before
  concatenation for deterministic output.
- is_official_dist_host: strip userinfo, port, and a trailing dot so an
  official nodejs.org URL reached through those forms still requires the
  signature; add look-alike negative tests.
- Document that primary_key_revoked honors self-revocations (Node release keys
  use no designated revokers).
@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests labels Jun 16, 2026
fengmk2 added 2 commits June 16, 2026 10:40
- Extract resolve_shasums_content() to flatten the nested match in
  download_runtime_with_provider: an early return for the no-signature case
  and a single match (verify / propagate-if-required / warn-and-fallback),
  with identical behavior.
- Remove the embedded_keys_parse test, which is subsumed by
  every_vendored_key_parses plus the >=28 floor in
  split_armored_blocks_finds_every_key.
Update HashVerification (signature/ShasumsSignature), the integrity section
(now PGP-verifies the clearsigned SHASUMS256.txt.asc against the Node release
keys, with mandatory/best-effort/musl-fallback behavior), the download-flow
diagram, and the success criteria. Cross-link verify-node-shasums-signature.md.
@fengmk2 fengmk2 changed the title feat(runtime): verify SHASUMS256.txt PGP signature before installing Node feat(runtime): verify Node.js downloads with PGP signatures Jun 16, 2026
The unofficial musl builds, and custom mirrors that publish only
SHASUMS256.txt, have no PGP signature, so verification falls back to the
SHA-256 checksum. Emit a user-facing warning on that path so users know the
download was not signature-verified. The previous tracing::warn was invisible
unless VITE_LOG was set, and the musl (no-signature) path warned nothing at
all.
Comment thread crates/vite_js_runtime/src/runtime.rs Outdated
fengmk2 added 2 commits June 16, 2026 12:41
…p in CI

Print the no-signature/checksum-only warning via the shared
vite_shared::output::warn helper (consistent styled "warn:" output) instead of
a raw eprintln, and suppress it when running in CI where it is just noise.
The no-signature path (unofficial musl builds, or a mirror that publishes only
SHASUMS256.txt) is an expected, non-actionable condition for those sources, not
an anomaly: on musl it would fire on every install. Record it with
tracing::debug! (visible via VITE_LOG) instead of a user-facing warning, which
also makes the CI suppression unnecessary.
@fengmk2

fengmk2 commented Jun 16, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: c3b82e26e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fengmk2 added 2 commits June 16, 2026 13:41
Add a "Prior art" section to the SHASUMS signature RFC comparing download
verification across nvm, fnm, Volta, asdf-nodejs, mise, and nodenv. Most stop
at SHA-256 checksums (or, for fnm, HTTPS only); only asdf-nodejs and mise also
verify the PGP signature, and both require an external gpg. Documents why a
built-in, dependency-free verifier justifies bundling rPGP and the keyring.
Allow skipping PGP signature verification of SHASUMS256.txt via the
VP_NODE_SKIP_SIGNATURE_VERIFY env var, so a future keyring or certificate
issue can be temporarily bypassed without blocking installs. The SHA-256
checksum is still verified (integrity preserved, only authenticity dropped),
and a warning is printed on every skipped install. Mirrors asdf's
NODEJS_CHECK_SIGNATURES and mise's signature opt-out; env-var only, no config
or CI flag, so the secure path stays the unconditional default.
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

✅ Staging deployment successful!

Preview: https://viteplus-staging.void.app/
Commit: 0776757

fengmk2 added 3 commits June 16, 2026 20:39
…ry fix

TEMP verification only: point at fengmk2/security-action@a2a9a4b (head of
oxc-project/security-action#22, which ignores RUSTSEC-2023-0071). Revert to the
upstream oxc-project pin once #22 is merged and released.
Reverts the temporary fork pin used to verify oxc-project/security-action#22.
Re-pin to a released oxc-project version that includes the RUSTSEC-2023-0071
ignore once #22 is merged and tagged.
@fengmk2 fengmk2 marked this pull request as ready for review June 19, 2026 01:39
@fengmk2 fengmk2 requested review from cpojer and sapphi-red June 19, 2026 01:40

@sapphi-red sapphi-red left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verify SHASUMS.txt before installing Node

3 participants