feat(cli): add verify and verify-signature commands - #2107
Merged
Conversation
Adds two new ovsx commands for checking a downloaded .vsix package's signature, addressing #993: - verify <extension.vsix>: resolves namespace/name/version from the package's own manifest, fetches the matching version's signature and public key from the registry (files.signature / files.publicKey, already exposed for any signed version), and verifies the Ed25519 signature over the raw package bytes using Node's built-in crypto - no server-side changes needed, and no proprietary signing dependency. On success it reports that the package is identical to the version published to the registry; on failure, that it doesn't match. - verify-signature -i <pkg> -m <manifest> -s <signature> -k <publicKey>: the offline counterpart, mirroring vsce verify-signature's exact command shape for a package/manifest/signature trio you already have on disk, with no registry involved. Unlike vsce, a public key must be supplied explicitly, since each Open VSX registry holds its own signing key rather than trusting one baked-in Marketplace key. Beyond the outer signature, it cross-checks the manifest's per-entry SHA256 digests against the package's actual contents and reports exactly which entry doesn't match, if any. Also bumps the minimum supported Node.js version to 22 (engines field and @types/node), matching webui, and adds readAllZipEntries to zip.ts - a case-preserving companion to the existing readZip, needed because manifest entry keys must match original (non-lowercased) file names. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds local verification capabilities to the ovsx CLI so users can confirm a downloaded VSIX is identical to what a registry published (online) or validate a package/manifest/signature trio entirely offline, aligning with VS Code/vsce signature verification behavior.
Changes:
- Add
ovsx verifyandovsx verify-signaturecommands (registry-backed vs fully offline verification). - Add zip helper for case-preserving entry reads to support manifest entry name matching.
- Bump CLI minimum Node.js version to 22 and update dependencies/tests accordingly.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/yarn.lock | Updates lockfile for Node 22 typings and new zip-building test dependency (yazl). |
| cli/test/unit/verify.spec.ts | Adds unit tests covering verify success and key/signature/unsigned failure modes plus allVersions resolution. |
| cli/test/unit/verify-signature.spec.ts | Adds unit tests for offline signature verification and detailed manifest mismatch reporting. |
| cli/src/zip.ts | Exports readZip and adds readAllZipEntries for case-preserving zip entry access. |
| cli/src/verify.ts | Implements registry-backed VSIX signature verification using registry-provided signature/public key. |
| cli/src/verify-signature.ts | Implements fully offline signature verification plus manifest digest cross-checks with detailed mismatch reporting. |
| cli/src/verify-signature-options.ts | Defines options interface for offline verification inputs. |
| cli/src/verify-options.ts | Defines options interface for registry-backed verification inputs. |
| cli/src/main.ts | Wires new verify and verify-signature commands into the CLI. |
| cli/README.md | Documents new verification commands and their intended usage. |
| cli/package.json | Bumps minimum Node.js engine version to 22 and adds dev deps for tests. |
| cli/CHANGELOG.md | Documents the new commands and Node.js version bump in the CLI changelog. |
Suppressed comments (1)
cli/src/verify-signature.ts:106
- When an entry exists in the manifest but is missing
digests.sha256, the current logic reports it as a digest mismatch. This is inaccurate and makes it harder to debug malformed manifests; report a clear "missing digest" issue and only compare when a digest is present.
if (entry.digests?.sha256 !== sha256Base64(content)) {
issues.push(`'${name}' does not match its digest in the manifest`);
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Distinguish a digest that's simply absent from the manifest from one that's present but wrong, for both the package-level and per-entry checks - a missing digest is a malformed/incomplete manifest, not necessarily a mismatch. - Stream each zip entry straight into a SHA256 hash instead of buffering its full content into a Map alongside the already fully loaded package bytes, roughly halving peak memory for large extensions. readAllZipEntries becomes hashAllZipEntries, returning digests directly since that's all any caller ever needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #993.
What
Adds two
ovsxCLI commands for checking a downloaded.vsixpackage's signature - no server-side changes needed, since the registry already exposes everything they need (files.signature/files.publicKeyon any signed version's metadata).ovsx verify <extension.vsix>Resolves the namespace/name/version from the package's own manifest, fetches the matching version's signature and public key from the registry, and verifies the Ed25519 signature over the raw package bytes using Node's built-in
crypto- the same check VS Code itself performs on install. No proprietary signing dependency required.✅ This package is identical to ns.ext v1.2.3 as published to <registry>.ovsx verify-signature -i <pkg> -m <manifest> -s <signature> -k <publicKey>The fully offline counterpart, mirroring
vsce verify-signature's exact command shape for a package/manifest/signature trio already on disk - no registry involved. The one deliberate deviation from vsce: a public key must be supplied explicitly (-k/--publicKeyPath), since each Open VSX registry holds its own signing key rather than trusting one baked-in Marketplace key.Beyond the outer signature, it cross-checks the manifest's per-entry SHA256 digests against the package's actual contents and reports exactly which entry doesn't match, is missing from the manifest, or is missing from the package - a level of diagnostic detail the signature check alone can't give.
Also in this PR
enginesfield +@types/node), matching what was already done forwebui.readAllZipEntriestozip.ts- a case-preserving companion to the existingreadZip, needed because manifest entry keys must match original (non-lowercased) file names.Testing
yarn build(tsc + lint) clean. Fullyarn test: 45/45 passing, including a real Ed25519 keypair/signature and hand-built zip fixtures for both commands (valid/tampered/wrong-key/unsigned cases,allVersionsresolution, and every manifest-mismatch case forverify-signature). Also manually smoke-tested both commands end-to-end against real generated fixtures.