feat(runtime): verify Node.js downloads with PGP signatures#1848
Conversation
…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
✅ Deploy Preview for viteplus-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
…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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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).
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
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).
- 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.
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.
…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.
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
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.
|
✅ Staging deployment successful! Preview: https://viteplus-staging.void.app/ |
…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.
|
deps on #1918 |
Release vite-plus v0.2.2: the Vite+ Beta release. **Vite+ is now in Beta**: stable and ready for production adoption, fully open source under MIT. Read the announcement to see what Vite+ is about and where it is headed: [Announcing Vite+ Beta](https://voidzero.dev/posts/announcing-vite-plus-beta). On top of the Beta milestone, this release brings cross-version upgrades via `vp migrate`, an official Docker toolchain image on GHCR, zero-config runner-aware `vp build` caching, and PGP-verified managed Node.js downloads. ### Highlights - **`vp migrate` upgrades existing Vite+ projects across versions**: previous release notes told users not to run `vp migrate` for upgrades. It now runs from the global CLI when the local one is older, re-pins `vite-plus` and the `vite` -> `@voidzero-dev/vite-plus-core` alias across dependencies, overrides/resolutions, and catalogs in every workspace package, aligns `vitest` / `@vitest/*` by actual usage, and defaults to a version-only upgrade (pass `--full` to also run the first-time setup bucket: hooks, editor, agent files, lint migration) ([#1891](#1891)), by @fengmk2 - **Official Vite+ Docker toolchain image**: `ghcr.io/voidzero-dev/vite-plus` bundles `vp` plus a native build toolchain on `debian:bookworm-slim` (amd64/arm64, non-root). Since `vp` provisions the exact Node.js from `.node-version`, one image builds any project, and a documented multi-stage build copies the resolved Node.js into a small vp-free runtime stage ([#1944](#1944)), by @fengmk2 - **Zero-config `vp build` caching via runner-aware Vite**: Vite reports its inputs, outputs, and tracked env reads to the `vp` runner over the new `@voidzero-dev/vite-task-client` IPC ([vite#22453](vitejs/vite#22453)), so `vp build` caches correctly with no hand-written cache config: outputs are tracked and restored automatically, and a changed `VITE_*` env var invalidates the cache and is named in the cache-miss message ([#1774](#1774)), by @wan9chi - **PGP-verified Node.js downloads**: installing a managed Node.js now verifies the release's clearsigned `SHASUMS256.txt.asc` against the vendored Node.js release keyring (pure Rust, no `gpg` required) before trusting any checksum, so a tampered archive is rejected before install; unsigned sources (musl builds, custom mirrors) fall back to checksum-only verification ([#1848](#1848)), by @fengmk2 ### Features - `vp check`: a `check` block in `vite.config.ts` (`check.fmt` / `check.lint`) can make plain `vp check` skip formatting or linting by default, mirroring `--no-fmt` / `--no-lint`; standalone `vp fmt` / `vp lint` and git hooks are unaffected, and a `note:` line keeps the config-based skip discoverable ([#1981](#1981)), by @fengmk2 - `vp env list-remote`: highlight installed versions (color, or a `*` prefix when piped) and label the project-resolved `current` and global `default` versions; `--json` gains `installed` / `current` / `default` fields ([#1907](#1907)), by @semimikoh - `vpr` ships as a `vite-plus` package bin, so the `vp run` shorthand works on clean installs without global PATH shims (Vercel build image, generic CI runners) ([#1988](#1988)), by @kvnwolf - Vite Task: `dependsOn` can select tasks from dependency packages, e.g. `dependsOn: [{ "task": "build", "from": "dependencies" }]` ([vite-task#479](voidzero-dev/vite-task#479)), by @wan9chi - Vite Task: a task's `env` / `untrackedEnv` glob patterns support `!` negation (e.g. `["VITE_*", "!VITE_SECRET"]`) ([vite-task#425](voidzero-dev/vite-task#425)), and an env-caused cache miss now names the variable inline, e.g. `cache miss: env 'NODE_ENV' changed` ([vite-task#438](voidzero-dev/vite-task#438)), by @wan9chi - Upgrade upstream dependencies: vite `8.0.16 -> 8.1.2`, rolldown `1.1.1 -> 1.1.4`, oxlint `1.70.0 -> 1.72.0`, oxfmt `0.55.0 -> 0.57.0`, oxlint-tsgolint `0.23.0 -> 0.24.0`, and the oxc toolchain `0.136.0 -> 0.138.0` ([#1924](#1924), [#1989](#1989), [#2000](#2000), [#2009](#2009)), by @voidzero-guard[bot] ### Fixes & Enhancements - Windows: `vp run` no longer hangs CI when a `node_modules/.bin` `.cmd` shim is routed through PowerShell; the npm/pnpm/yarn `.ps1` wrappers read stdin and block forever on a non-TTY pipe, so the PowerShell rewrite is now skipped when stdin is not an interactive terminal ([vite-task#491](voidzero-dev/vite-task#491), via [#1973](#1973)), by @fengmk2 - Vite Task: the task cache is stored in a per-schema-version directory (e.g. `node_modules/.vite/task-cache/v13/`), so switching between branches that pin different Vite+ versions no longer fails with `Unrecognized database version` ([vite-task#433](voidzero-dev/vite-task#433)), by @fengmk2 - Vite Task: env values in cache fingerprints are stored only as SHA-256 digests and env cache-miss details report names without values ([vite-task#455](voidzero-dev/vite-task#455)); prefix env assignments like `PATH=... command` now affect executable lookup during planning ([vite-task#440](voidzero-dev/vite-task#440)); `package.json` / `pnpm-workspace.yaml` files with a UTF-8 BOM parse correctly ([vite-task#424](voidzero-dev/vite-task#424)), by @wan9chi - `vp upgrade`: run the pinned pnpm with a managed Node.js LTS directly instead of re-entering `vp install`, so an incompatible session/project/system runtime can no longer make pnpm skip optional native binaries and leave the upgraded CLI broken ([#1900](#1900)), by @liangmiQwQ - Global package installs: each install writes to an immutable `packages/<name>#<uuid>` prefix that is activated via metadata after npm succeeds, so an interrupted reinstall can no longer leave the active package unavailable ([#1906](#1906)), and stale interrupted-install directories are swept with file-lock protection for concurrent installs ([#1945](#1945)), by @liangmiQwQ - `lazyPlugins()`: skip plugin factories only while config metadata is being resolved instead of keying off `VP_COMMAND`, so builds spawned under `vp run` / `vp exec` keep the user's plugins and `vp format` no longer loads them ([#1939](#1939)), by @fengmk2 - `vp migrate` (pnpm): add a direct `vite` devDep aliased to the core override wherever `vite-plus` is depended on, so vitest's `vite` peer binds to `@voidzero-dev/vite-plus-core` instead of pulling in a second upstream vite that broke the `vp test` cache ([#1933](#1933)), by @fengmk2 - `vp pack`: bundle `@tsdown/exe` and `@tsdown/css` into core so `--exe` and CSS bundling work without a resolvable top-level `tsdown`; the native `lightningcss` becomes an optional peer loaded lazily with an actionable error ([#1919](#1919)), by @fengmk2 - `vp env`: invalidate stale shim resolve cache entries when the project's Node.js version source changes ([#1951](#1951)), by @jong-kyung - Node shim: when the project declares npm via `packageManager` / `devEngines.packageManager`, child processes spawned from `node` resolve the managed npm instead of the Node-bundled one ([#1938](#1938)); `vp env which` reports bins linked by an intercepted `npm install -g` (e.g. `tsc`) instead of "not found" ([#1968](#1968)); bins with uppercase names (e.g. `vitePlus`) dispatch correctly ([#1963](#1963)), by @liangmiQwQ - `vp-setup`: pass the configured npm registry to the inner pnpm install so setup works behind custom registries ([#1795](#1795)), by @daflyinbed - Native binding: declare the platform packages' true ABI floor `engines.node >=20.0.0` so engine-strict package managers (pnpm) no longer skip the optional native dependency and fail with `Cannot find native binding` when a consumer's Node floor lands in a product-policy gap ([#1993](#1993)), by @fengmk2 - `vp create`: run `git init` without creating an initial commit, so commitlint-configured templates no longer reject the hardcoded message and template placeholders are not baked into history ([#2008](#2008)), by @forehalo - `vp staged --debug`: inline the bundled lint-staged version so debug logging no longer crashes reading a `package.json` that does not exist in the bundle ([#1925](#1925)), by @rokuosan - Installer: retry downloads truncated mid-body in `HttpClient::get_bytes` (the platform-tarball path for `vp upgrade` and the standalone installer) ([#1940](#1940)), and clean up the temp dir when a package-manager install fails instead of leaking `.tmpXXXX` directories ([#1949](#1949)), by @shulaoda - Windows/msys: normalize backslashes in the `env.fish` fallback path ([#1954](#1954)), by @Aalivexy - `install.ps1`: detect the missing VC++ runtime (`0xC0000135`) and print VC++ Redistributable guidance instead of a generic failure; interactive `irm | iex` installs keep the shell open ([#1962](#1962)), by @cheezone - `vp migrate`: preserve comments, key order, and trailing commas in existing `.vscode` / `.zed` JSONC configs by patching the original text instead of re-serializing it ([#1956](#1956)), by @fengmk2 - Migration: link the git hook warning to the migration guide ([#1902](#1902)), by @naokihaba - `vp info` / `vp view`: use package-manager-native commands (`pnpm view`, `bun info`, `yarn npm info`) instead of routing every lookup through `npm view` ([#1895](#1895)), by @jong-kyung - Correct overused `ErrorConfig` error types across the codebase ([#1934](#1934)), by @liangmiQwQ ### Refactor - `vite_install`: centralize Yarn v1/berry branching with `is_yarn_berry` ([#1897](#1897)), by @jong-kyung ### Docs - Document Vite Task automatic tracking (fs tracking and cache-reporting tools), reusing the task cache with GitHub Actions cache, and `dependsOn: [{ task, from: "dependencies" }]` ([#1992](#1992)), by @wan9chi - Rewrite the "Upgrading Vite+" guide: preview builds install through the registry bridge as ordinary `0.0.0-commit.<sha>` npm versions, and `vp migrate` is the recommended way to upgrade a project or move it onto a preview build ([#1965](#1965)), by @fengmk2 - Describe how to switch back to the release version from nightly ([#1887](#1887)), by @situ2001 - Clarify Git hook tool migration ([#1901](#1901)), by @naokihaba - Add a global installation explanation ([#1915](#1915)), update the `vp env` help output ([#1969](#1969)), and add liangmiQwQ as a team member ([#1911](#1911)), by @liangmiQwQ - Fix package manager command examples ([#1937](#1937)) and the `dependsOn` guide link ([#1883](#1883)), by @jong-kyung - Remove Fathom analytics from the uninstall docs ([#1946](#1946)), by @mdong1909 - Center the README logo and fix its size ([#1878](#1878)), by @hyf0 ### Chore - Prevent Vite beta upgrades in the upstream-deps script ([#1879](#1879)), stabilize flaky network-dependent tests ([#1923](#1923)), and bump the ecosystem-ci bun-vite-template to the oxlint jsx-a11y fix ([#1898](#1898)), by @fengmk2 - Pin snap-test install fixtures to a published `vite-plus` version so release-branch CI can pass before the new version is published ([#2017](#2017)), by @fengmk2 - Update the deprecated `VITE_PLUS_` env var prefixes to `VP_` in the RFCs ([#1984](#1984)), by @liangmiQwQ - CI: skip full CI for docs-only PRs ([#1991](#1991)) and for the docs deploy config ([#2015](#2015)), by @wan9chi - Update GitHub Actions ([#1904](#1904), [#1977](#1977)), claude-code-action to v1.0.158 ([#1979](#1979)), and pnpm to v10.34.4 ([#1996](#1996)), by @renovate[bot] - Update oxc-project/security-action to v1.0.8 ([#1918](#1918)), by @fengmk2 - Refresh trusted stack stats on the docs homepage ([#1913](#1913), [#1982](#1982)), by @voidzero-guard[bot] ### Bundled Versions | Tool | Version | Source | | --- | --- | --- | | vite | `8.1.2` | [`ba31193`](vitejs/vite@ba31193) | | rolldown | `1.1.4` | [`6cbd233`](rolldown/rolldown@6cbd233) | | tsdown | `0.22.3` | [npm](https://npmx.dev/package/tsdown/v/0.22.3) | | vitest | `4.1.9` | [npm](https://npmx.dev/package/vitest/v/4.1.9) | | oxlint | `1.72.0` | [npm](https://npmx.dev/package/oxlint/v/1.72.0) | | oxlint-tsgolint | `0.24.0` | [npm](https://npmx.dev/package/oxlint-tsgolint/v/0.24.0) | | oxfmt | `0.57.0` | [npm](https://npmx.dev/package/oxfmt/v/0.57.0) | ### Upgrade ```bash vp upgrade ``` New to Vite+? Start with the [Beta announcement](https://voidzero.dev/posts/announcing-vite-plus-beta), then create a project with `vp create` or bring an existing one over with `vp migrate`. ### New Contributors Welcome to our new contributors @rokuosan, @Aalivexy, @cheezone, @daflyinbed, @forehalo, @kvnwolf! 🎉 **Full Changelog**: v0.2.1...v0.2.2 --- Merging this PR will trigger the release workflow. --------- Co-authored-by: voidzero-guard[bot] <278573678+voidzero-guard[bot]@users.noreply.github.com> Co-authored-by: MK <fengmk2@gmail.com>
When
vpinstalls a managed Node.js version, it now verifies the release's PGP signature (the clearsignedSHASUMS256.txt.asc) against Node's release keys before trusting any checksum. Previously the plainSHASUMS256.txtwas trusted as-is, so anyone able to tamper with it could swap in a matching malicious archive.This is the same guarantee as
gpgvagainst the official keyring, built in (pure Rust, nogpgrequired). The keyring is vendored fromnodejs/release-keysand kept current by a weekly workflow.Example output
A normal install is unchanged (verification is transparent):
A tampered or untrusted SHASUMS is rejected before anything is installed:
The official
nodejs.orgsource always requires a valid signature. The unofficial musl builds, and custom mirrors that publish onlySHASUMS256.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
.nodeNAPI binding (vite-plus-cli) is unaffected: no size change.pgpand the vendored keyring are reachable only throughvite_js_runtime, which is linked into the globalvpbinary, not into the binding (vite_install/vite_pm_cli). Only the globalvpbinary grows, by ~1.2 MiB (rPGP plus the RustCrypto verification code and the embedded release keyring).Closes #1807