vscode-extensions: add optional Marketplace signature verification#482821
vscode-extensions: add optional Marketplace signature verification#482821cavebatsofware wants to merge 2 commits into
Conversation
9eb82ec to
a65411f
Compare
|
Tagging a few committers that have recently reviewed VSCode related PRs in case anyone has time to take a look at this VSCode Marketplace Signature Verification support change. |
|
Squash the formatting commit into the prior one. See: https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#commit-conventions |
a65411f to
4d68cf2
Compare
@cybardev Fixed the commit history, thank you for taking a look. Also added some brief documentation in the README for vscode-extensions so the feature is documented. |
4d68cf2 to
1c6b76e
Compare
|
@cybardev Also removed support for the signatureSha256 pattern after realizing in the code and documentation that the sha256 pattern is deprecated and it seems like continuing a deprecated pattern would have been undesirable. |
|
These action failures are unexpected and the errors look CI / workflow related rather than code related. |
|
@cavebatsofware Sorry for the late reply, and great work. Tried testing this on Darwin with the following hook patch: diff --git a/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh b/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
index dd38a4ccea69..f693c23b02f8 100644
--- a/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
+++ b/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
@@ -14,7 +14,14 @@ _verifyVsixSignaturePreUnpack() {
return 0
fi
- local vsceSign="@vscode@/lib/vscode/resources/app/node_modules/@vscode/vsce-sign/bin/vsce-sign"
+ local vsceSign
+ if [[ -d "@vscode@/Applications" ]]; then
+ # macOS .app bundle layout
+ vsceSign="@vscode@/Applications/Visual Studio Code.app/Contents/Resources/app/node_modules/@vscode/vsce-sign/bin/vsce-sign"
+ else
+ # Linux layout
+ vsceSign="@vscode@/lib/vscode/resources/app/node_modules/@vscode/vsce-sign/bin/vsce-sign"
+ fi
if [[ ! -x "$vsceSign" ]]; then
echo "WARNING: vsce-sign not found at $vsceSign - signature verification skipped" >&2The signature process works fine, but only on Darwin systems with disabled sandbox - |
|
Thank you for taking a look at this and catching this issue on Darwin. I will fix this and confirm it works on a local mac with relaxed or enabled sandbox.
…________________________________
From: Matteo Pacini ***@***.***>
Sent: Monday, April 13, 2026 6:40 AM
To: NixOS/nixpkgs ***@***.***>
Cc: Grant DeFayette ***@***.***>; Mention ***@***.***>
Subject: Re: [NixOS/nixpkgs] vscode-extensions: add optional Marketplace signature verification (PR #482821)
[https://avatars.githubusercontent.com/u/3139724?s=20&v=4]matteo-pacini left a comment (NixOS/nixpkgs#482821)<#482821 (comment)>
@cavebatsofware<https://github.com/cavebatsofware> Sorry for the late reply, and great work.
Tried testing this on Darwin with the following hook patch:
diff --git a/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh b/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
index dd38a4ccea69..f693c23b02f8 100644
--- a/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
+++ b/pkgs/applications/editors/vscode/extensions/verify-vsix-signature-setup-hook.sh
@@ -14,7 +14,14 @@ _verifyVsixSignaturePreUnpack() {
return 0
fi
- local ***@***.***@***@***.***/vsce-sign/bin/vsce-sign"
+ local vsceSign
+ if [[ -d ***@***.***@/Applications" ]]; then
+ # macOS .app bundle layout
+ ***@***.***@/Applications/Visual Studio ***@***.***/vsce-sign/bin/vsce-sign"
+ else
+ # Linux layout
+ ***@***.***@***@***.***/vsce-sign/bin/vsce-sign"
+ fi
if [[ ! -x "$vsceSign" ]]; then
echo "WARNING: vsce-sign not found at $vsceSign - signature verification skipped" >&2
The signature process works fine, but only on Darwin systems with disabled sandbox - relaxed and enabled would require more work.
—
Reply to this email directly, view it on GitHub<#482821 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAEH3FJ7BXTSVAKTEAT7GLT4VS7ZTAVCNFSM6AAAAACSTBGWC6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DEMZVG42DKNZTHE>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
@cavebatsoftware looking good! A couple of nits:
Here's the logic re: commits: "If this commit introduces a problem downstream, can I revert this one commit safely?" Also "is this commit self-contained and readable enough that someone can understand what I did?" |
FYI, Hydra builds all Darwin packages with the sandbox off. Having something work on a restricted sandbox is nice, but not required for Darwin. ( |
|
|
|
Don't worry about |
I did make changes that ensure that relaxed sandboxes work. Restricted sandboxes require an override: (pkgs.vscode-extensions.publisher.extension.override { allowMissingVsceSign = true; }) |
9e9ba11 to
42e0aeb
Compare
|
updated second commit message lead to "vscode-extensions.anthropic.claude-code:" |
42e0aeb to
7a6e0b2
Compare
|
re-based on master again to resolve conflicts (this happens when the auto-update scripts run, and will continue to happen until this can merge) |
7a6e0b2 to
7bc6094
Compare
|
@sarahec I really appreciate you taking a look at this and drawing some attention. @Zaczero I really appreciate your initial review when this wasn't getting any looks among many other things. If I had the clearance I would certainly help review and get things approved. I understand your frustration. We are in it together, you have support in your efforts, even when things are not moving as quickly as we would like. This change means a-lot to me as it fixes a notable security issue for a very popular userspace application. An issue that in some environments would otherwise violate security guidelines, ie in this org users can use vscode but not if the installation method bypasses signature verification of installed extensions etc, and it is just something I like to have as piece of mind, which is why I developed this for my own use. I feel like this is a reasonable contribution to the security acceptance argument of nixos. I really love and appreciate all the time contributors that work on this repo give. Everyone is doing so much and putting so much out there that it IS hard to keep up with but that is a great problem to have; and I think that it's a problem we'll solve together. |
|
I'm following this too, I just don't think I know enough about nix or vscode/extensions to actually review this. All I've done so far is package kiro, and even that still fails the darwin builds (which I have no idea how to fix, and can't test because I don't own a Mac). |
@Vuks69 I have an M4 mac-mini that I could probably use to take a look at the kiro build issue, separately from this PR of course. |
MattSturgeon
left a comment
There was a problem hiding this comment.
Docs should follow one-sentence-per-line
This makes reviews and suggestions much easier, since GitHub's review system is based on lines.
It also helps identifying long sentences at a glance.
@MattSturgeon I have gone through my readme changes and updated them as recommended to adhere to the documentation standards. Thanks for looking this over. |
MattSturgeon
left a comment
There was a problem hiding this comment.
Reviewed the nix side of the PR. SGTM overall, some design questions and nitpicks. I didn't look at the python impl in any detail.
| Signature verification runs only when the `vscode` package exposes `passthru.hasVsceSign = true`. | ||
| Microsoft's build does; `vscodium` does not because it ships without the `vsce-sign` binary). | ||
| Users who overlay `vscode = vscodium` or other variant without `vsce-sign` get verification automatically skipped. |
There was a problem hiding this comment.
Q: does this need to be flagged via a passthru attr, or is it possible to probe for vsce-sign support during the build?
E.g. we could do something like
if command -v vsce-sign > /dev/null; then
# TODO: validate signature
else
echo "Warning: vsce-sign is not available" >&2
fi(EDIT: reading down, this is essentially your [[ ! -x "$vsceSign" ]] branch).
I guess it's still useful to have an attr define whether the else branch here should exit, error, warn, or log quietly?
There was a problem hiding this comment.
This is part of the vscode/other package it doesn't need passthrough in the extension since it is used elsewhere and was already available. Although that may not fully answer your question.
There was a problem hiding this comment.
I was essentially asking whether you could do an automatic built-time check for "is vsce-sign available" without having the eval-time attribute hasVsceSign.
The advantage of the attribute is that you can error if the attribute is true but the exe is not available.
| # Fetch signature archive for cryptographic verification | ||
| fetchSignatureFromVscodeMarketplace = | ||
| mktplcExtRef: | ||
| let | ||
| inherit (mktplcExtRef) publisher name version; | ||
| arch = mktplcExtRef.arch or ""; | ||
| archurl = if arch == "" then "" else "?targetPlatform=${arch}"; | ||
| baseUrl = "https://${publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${publisher}/extension/${name}/${version}/assetbyname"; | ||
| signatureHash = mktplcExtRef.signatureHash or ""; | ||
| in | ||
| if signatureHash != "" then | ||
| fetchurl { | ||
| url = "${baseUrl}/Microsoft.VisualStudio.Services.VsixSignature${archurl}"; | ||
| name = "${publisher}-${name}.sigzip"; | ||
| hash = signatureHash; | ||
| } | ||
| else | ||
| null; |
There was a problem hiding this comment.
This seems extremely similar to the existing mktplcExtRefToFetchArgs impl. Maybe a future refactor could share common code and return either a extension or signature depending on inputs? Or even return both FODs in a tuple attrset. Or return the extension FOD with its signature FOD as a passthru.
I'd personally consolidate something like:
{
publisher,
name,
version,
arch ? "",
sha256 ? "",
hash ? "",
signatureHash ? "",
}@mktplcExtRef:
let
archurl = (if arch == "" then "" else "?targetPlatform=${arch}");
baseUrl = "https://${publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${publisher}/extension/${name}/${version}/assetbyname";
in
fetchurl {
# VSIX package fetch arguments (for fetchurl)
url = "${baseUrl}/Microsoft.VisualStudio.Services.VSIXPackage${archurl}";
inherit sha256 hash;
# The `*.vsix` file is in the end a simple zip file. Force it using .vsix extension
# so that existing `unpackVsixSetupHook` hooks takes care of the unpacking.
name = "${publisher}-${name}.vsix";
passthru = {
${if signatureHash == "" then null else "signature"} = fetchurl {
url = "${baseUrl}/Microsoft.VisualStudio.Services.VsixSignature${archurl}";
name = "${publisher}-${name}.sigzip";
hash = signatureHash;
};
};
}Removing the overly-abstracted mktplcExtRefToFetchArgs.nix file unless it is actually used by several places.
There was a problem hiding this comment.
This could probably be improved. I'm fairly new with nix so I probably didn't implement this as cleanly as I probably should have. I will attempt to clean this up some when I address your other findings.
There was a problem hiding this comment.
@MattSturgeon I made a small consolidation around the URL but avoided a larger refactor to avoid impacting vscodeExts2nix.nix which also uses this function. Switching the file to return a fetchurl derivation would require updating vscodeExts2nix.nix's call site. Which should probably go in another PR that is more focused on refactoring these patterns. Although I'm not opposed to doing that here if that is the general consensus.
For the dedup itself I extracted a small mktplcAssetBaseUrl helper in vscode-utils.nix:
mktplcAssetBaseUrl =
{ publisher, name, version, ... }:
"https://${publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${publisher}/extension/${name}/${version}/assetbyname";fetchSignatureFromVscodeMarketplace now uses it instead of inlining the literal. The duplication shrinks without moving the public shape of mktplcExtRefToFetchArgs.nix
There was a problem hiding this comment.
Ideally we'd consolidate these two functions, but I agree it is beyond the scope of this PR.
MattSturgeon
left a comment
There was a problem hiding this comment.
Sorry for the delay. I was waiting for 26.05 branch off; master is now on 26.11!
I had another quick skim through and didn't see any major issues. I left some minor comments and there are merge conflicts that need rebasing, though.
Ideally, someone more familiar with the vscode infrastructure would take a look (do you know anyone we can ping?). But otherwise, I'm happy to merge once you're confident.
| "eval", | ||
| "--raw", | ||
| "-f", | ||
| ".", |
There was a problem hiding this comment.
I believe this depends on the current working directory. Ideally we'd use a fixed path, relative to the current Path(__file__)
There was a problem hiding this comment.
This is moved existing code, so not necessarily this PR's job to fix.
| "nix-command", | ||
| "eval", | ||
| "--raw", | ||
| "-f", |
There was a problem hiding this comment.
My personal preference is for persistent scripts to avoid "short" flags in favour of the more descriptive long flags
| "-f", | |
| "--file", |
| Fetches the signature hash for an extension version from the VS Code Marketplace. | ||
| Returns the SRI hash or None if fetch fails. | ||
| """ | ||
| platform_suffix = f"?targetPlatform={target_platform}" if target_platform else "" |
There was a problem hiding this comment.
This is fragile if it is possible that a target_platform string could ever contain URI syntax chars. Unlikely, but best practice would be to build the query parameter string using a URI library, or at least URI-escape the value.
Not blocking here.
Ill take a look at (and correct) all of the issues you identified in your last review. Good finds, I agree with pretty much all of your comments and appreciate the feedback. As far as "anyone that I can ping that is familiar with this package infrastructure" well, I don't know of anyone that actually knows how this works other than myself. I know there are maintainers etc but I personally have not seen anyone (that is a maintainer) have any interest in this PR. The merge conflicts are very easy to solve obviously and only need a vscode update script run to fix. I have been doing that regularly as the force-push records reflect but I am not going to do that any more until someone is actually going to merge this. |
|
(I am not very knowledgeable about VS Code or our extensions infrastructure.) Do I understand correctly that the keys are bundled in In that case, this only provides any security guarantees over the current “downloading from Microsoft servers” if we can gain trust in the VS Code package itself beyond “downloading from Microsoft servers”, right? Otherwise the security reduces to the same TLS but with extra steps (admittedly only on every VS Code update rather than every extension update, but it seems like fundamentally the same MITM task to me). So we’d need to verify updates to the VS Code package itself somehow for this to have a benefit. Is that possible? Are the VS Code packages themselves signed in a way that we could verify (e.g. with the previous version’s |
|
After doing some more research: It seems that the Using the |
Without getting into exactly how vsce-sign actually works, which would be guesswork on my part, beyond what has been publicly documented, I will try to address the other concerns in these last comments. Regarding unfree concerns etc; vscode itself is "unfree" and any extension signing support is only used when vsce-sign is available. In other words, this has provisions to allow vscodium to use extensions normally as it does already. Regarding speculation as weather or not this provides any real additional security; I believe that this at the very least lets the vscode application verify signatures as the application normally would when installing extensions from the marketplace directly. Nixos has a hash element for the extension itself but the signature is the cannonical way to validate that the Marketplace has actually signed the package. This is different than just checking the packages marketplace metadata and getting a json result that claims that the package was signed. The latter is a filter the former is an actual signature by microsoft. I didn't validate how Microsoft has designed vsce-sign and I actually don't think that should be a concern of this PR. This PR just allows VSCode extension builds on Nixos to actually validate the extensions the same way the vscode application does. I don't think the package builder should skip things like this just because we have another hash that we think is as good as the built in application extension verification. If Nixos is going to skip this verification it seems like something that should be documented as intentional, so users are aware that this check is being bypassed. |
You seem to misunderstand the concern.
Does the Marketplace host unsigned packages? We download directly from Microsoft's servers.
It makes a difference if the signing tool has to be used in the build. Building a signed package for Nix invalidates the old signature, so we have to re-sign it. (Emily, who posted the comment above, is one of our Darwin leads.)
Maybe I'm missing something. Can you share a proof of concept where the tools built with this patch correctly validate nix-built extensions? That would set everyone's mind at ease.
Can you show an example where the Nix validation passes on an incorrect extension (from Marketplace) but the signature check catches it? |
|
@sarahec I have read your and @emilazy comments and questions a bit more closely, done some more detailed research on the process and testing with vscodium which confirmed that the concerns related to Hydra and vscodium user (depending on how they are configuring it) impacts are definitely valid. There is somewhat of an in-between option for doing this in the update script on the maintainer box, or on the user box. It seems like this should run on the maintainer box at update time and if a vscode user wants to run this verification locally (extension build) they can do so. EDIT: Vscodium users could also, if they wanted to, use the vscode signature verification IF they want to do it. They WOULD have to make the compromise of allowing unfree for that build process, but would not have to fully install vscode (aside from its build requirement) on their system to do so. Just noting that it is possible for vscodium users to use this but it comes with some tradeoffs for them if they are trying to run a fully FOSS install END EDIT. FOSS packages would then still work just fine with signatureHash set. Regarding some of the other questions:
Yes, Marketplace does host unsigned packages. This can happen when a packages is in per-release or fails validation or virus scan and possible other reasons that I am not aware of. I created a gate here #522773 to prevent this on update but that doesn't actually validate the signature and depends on the json response from the MS server to be valid and not change in structure or meaning.
Same as the above reference: #522773 Updates:Instead of running this verification automatically I have refactored it so it is manual and can be done at update time by the maintainer, or a dedicated CI that has verification and unfree support enabled. The existing CI tools should remain fully functional for FOSS extensions that have signatureHash enabled. Users or package maintainers (of unfree or FOSS packages) can enable signatureHash without forcing verification by letting users opt-in with config.vscodeExtensions.verifySignature and/or unfree packages can set verification on by default individually. The improvement here is now the maintainer can easily validate all the different platform binaries signature at the same time without having to actually be on those platforms. This is just because running vsce-sign in various sig/archs works independent of build env arch. So all target platforms are verified in a single update as expected. |
…ation Adds opt-in cryptographic signature verification for VS Code Marketplace extensions. Setting `signatureHash` on `mktplcRef` pins the extension's signature archive (a fixed-output derivation) alongside the VSIX hash. Build-time verification is opt-in via `verifySignature`, enabled per extension or globally with `vscodeExtensions.verifySignature = true` in nixpkgs config. It checks the VSIX against its signature with Microsoft's `vsce-sign` (bundled with VS Code) in `preUnpackHooks`. Because `vsce-sign` is unfree, this is off by default so signed extensions stay free-buildable on Hydra and usable with vscodium; it is also skipped when the `vscode` package does not expose `passthru.hasVsceSign = true`. `vscode_extension_update.py` verifies signatures at update time, sharing one verifier (`verify-vsix-signature.sh`) with the build hook so both honour the same `vsce-sign` invocation and exit codes. `--with-signature` fetches, adds, and verifies `signatureHash` on every platform, and `--skip-verify` populates hashes without verifying; both are also accepted via `VSCODE_EXTENSION_WITH_SIGNATURE` and `VSCODE_EXTENSION_SKIP_VERIFY`, since update runners (nix-update, update.py) pass the environment through but cannot forward CLI flags. Verification respects the ambient unfree policy. Routine updates auto-detect and refresh an existing `signatureHash` without verifying, so automated updaters such as r-ryantm keep signatures current without pulling the unfree `vsce-sign`.
Updates hash values to reflect new version 2.1.170 of the Anthropic Claude Code VSCode extension. Adds signature hash for verification. Introduces signatureHash fields across all platform architectures to enable cryptographic verification of downloaded extensions, ensuring extension integrity and authenticity from the marketplace
vscode-extensions: add opt-in Marketplace signature verification
Adds opt-in cryptographic signature verification for VS Code Marketplace extensions, honoring the same signing contract VS Code enforces as a layer alongside hash pinning.
It is designed to not disrupt the free build path: verification uses Microsoft's unfree
vsce-sign, so it is off by default and is never pulled into the default extension build, Hydra, or vscodium. Unfree is only ever reached through an explicit opt-in.Relates to #522775
While resolving merge conflicts for this work I found it is possible to update to and install an extension whose signature is unavailable. #522775 filters those out via marketplace metadata; this PR adds actual cryptographic verification of the signature itself.
How it works
Pinning (always free). Setting
signatureHashonmktplcReffetches and pins the extension's signature archive (a fixed-output derivation) alongside the VSIXhash. This alone pulls nothing unfree.Build-time verification (opt-in). Set
verifySignature = trueon an extension, orvscodeExtensions.verifySignature = truein nixpkgs config to cover every signed extension. The build then verifies the VSIX against its signature withvsce-signinpreUnpackHooks. Becausevsce-signis unfree it is off by default, so signed extensions stay free-buildable on Hydra and usable with vscodium. It is also skipped automatically when thevscodepackage does not exposepassthru.hasVsceSign = true(e.g. vscodium), keeping that path free regardless of the flag.Update-time verification. The update script and the build hook share one verifier (
verify-vsix-signature.sh), so both honor the samevsce-signinvocation and exit codes.--with-signaturefetches, adds, and verifiessignatureHashon every platform (cross-arch from a single host);--skip-verifypopulates hashes without verifying. Both are also accepted viaVSCODE_EXTENSION_WITH_SIGNATURE/VSCODE_EXTENSION_SKIP_VERIFYenv vars, sincenix-update/update.pypass the environment through but cannot forward CLI flags. Verification respects the ambient unfree policy and never forcesallowUnfree.Routine updates auto-detect and refresh an existing
signatureHashwithout verifying, so automated updaters such as r-ryantm keep signatures current without pulling the unfreevsce-sign.What changed
verify-vsix-signature.shshared verifier (single source of truth) wrappingvsce-sign verifywith exit-code handling.verify-vsix-signature-setup-hook.shthinpreUnpackHookswrapper around the shared verifier.vscode-utils.nixverifySignature(per-extension or globalconfig.vscodeExtensions.verifySignature) gates the hook;signatureArchiveis fetched fromsignatureHash; exportsvsceSignExefor the update script.vscode_extension_update.py--with-signature/--skip-verify(and env equivalents), per-platform update time verification,--forceto re-fetch on unchanged versions, auto-detect/refresh of existing hashes without verifying.vscode(generic.nix/vscode.nix)passthru.hasVsceSign.Usage
Opt in per extension:
Manage signatures with the update script:
Verification in action
Passing build
verifySignature = true:A VSIX that does not match its signature (the case
hash-pinning alone cannot catch) fails the build:
Cross-arch update-time verification: a darwin-arm64 VSIX verifies
PASSED on an x86_64-linux host, so one machine can verify all
platforms.
Backwards compatibility
Fully backwards compatible. Extensions without signatureHash are
unchanged. Extensions with signatureHash but no verifySignature build
exactly as before (free), with the signature pinned and verifiable on
demand. No extension is forced to pull vsce-sign unless it (or the
user's config) opts in.
Things done
nixpkgs-reviewon this PR../result/bin/.