Your editor will happily install an extension an attacker planted. vsxprint checks the ones you already have — or the ones a repo is about to recommend to you — against the supply-chain tricks that actually landed in 2025 and 2026: namespace-squatting on Open VSX, unverified or unrelated publishers, and GlassWorm-style payloads hidden inside invisible Unicode.
It reads your installed extensions straight off disk (VS Code, Cursor, Windsurf, VSCodium, Trae, Antigravity — no editor launch needed), asks Open VSX who really owns each namespace, and greps the extension bundles for hidden code. One Go binary, nothing to install alongside it.
$ vsxprint ~/src/vscode # audit a repo's recommended extensions before you accept them
vsxprint 0.1.0 — extension supply-chain audit
Scanned: workspace recommendations (12) — 12 extensions
Findings: 2 high, 0 medium
HIGH typescriptteam.native-preview Unpublished extension in an unverified namespace
HIGH connor4312.esbuild-problem-matchers Unpublished extension in an unverified namespace
Those two are live in microsoft/vscode's own .vscode/extensions.json right now: both namespaces are unverified and empty on Open VSX, so an AI-IDE fork that resolves typescriptteam.native-preview there installs whatever an attacker publishes into that unclaimed namespace — the exact shape of the January 2026 Koi attack. Run with -v and it names the namespace state behind each one.
AI-IDE forks — Cursor, Windsurf, Antigravity, Trae — inherit Microsoft's recommended and built-in extension lists, but they can't use the Microsoft Marketplace, so they resolve installs against Open VSX. A lot of those publisher.name namespaces were never claimed there. In January 2026 researchers registered a few of the unclaimed ones as a proof of concept; a placeholder for the recommended PostgreSQL extension picked up around 500 installs before anyone noticed. If the namespace is open, the extension your fork installs isn't necessarily the one you think it is.
And in October 2025, GlassWorm became the first self-propagating VS Code worm on Open VSX — it hid its payload in invisible Unicode (variation selectors mapped to bytes, then eval'd) so the malicious code was literally not visible in the file. vsxprint looks for that encoding, and for the Trojan Source bidi-override trick (CVE-2021-42574) that reorders how code displays versus how it runs.
The pattern is not slowing down: between late July and early August 2026, researchers catalogued 77 "evil twin" Open VSX extensions that republished a real Marketplace extension's name and namespace under an account that didn't own it, 19 of them exfiltrating git remotes, commit emails and CI environment variables. That is exactly the unrelated-publisher case vsxprint flags.
go install github.com/NotChaosuu/vsxprint/cmd/vsxprint@latestGo 1.22+. No other dependencies.
vsxprint # audit everything installed across detected editors
vsxprint --editor Cursor # just one editor
vsxprint --extensions-dir DIR # also scan a fork's extensions dir that isn't auto-detected
vsxprint . # audit a repo's .vscode/extensions.json recommendations
vsxprint --drift # also cross-check the Marketplace for publisher drift (slower)
vsxprint --offline # no network — only scan installed files for hidden payloads
vsxprint --registry URL # point at an Open VSX mirror
vsxprint --json | jq # machine-readable (findings is always an array)
vsxprint --exit-code --min-severity high # CI gatePointing it at a folder audits that repo's recommended extensions before you accept the "install recommended extensions" prompt — which is exactly where the namespace-squat attack lands.
On failure it stays loud. A network error, an oversized file, or a bad flag never reads as a clean scan: failed lookups print a WARNING: N registry lookup(s) failed — this scan is INCOMPLETE to stderr, skipped files are noted, and --exit-code returns 2 for an incomplete scan (versus 1 for real high/medium findings). "I couldn't check" and "I checked and it's clean" are different exit codes.
Namespace-squatting. For every extension id, vsxprint asks Open VSX about the publisher namespace. A 404 means the namespace is unclaimed — anyone can register it and publish under that id, and a fork that recommends it would pull their copy. An extension that's absent from an unverified namespace is the same live hijack path and is also flagged high; the same absence in a verified namespace is only a coverage gap (low), because the owner controls it. Severity tracks who holds the namespace, not just whether the extension is published.
Open, unverified, and unrelated publishers. A namespace marked public on Open VSX lets anyone publish into it. One with no verified owner has an unconfirmed publisher. And Open VSX's own unrelatedPublisher flag catches the case where a namespace's extension was uploaded by someone unrelated to the owner (the vesper case above).
Publisher drift (--drift). Cross-references the VS Code Marketplace. If an extension is domain-verified on the Marketplace but unclaimed or unverified on Open VSX, the copy your fork installs from Open VSX isn't the trusted one.
Hidden payloads. Every installed extension's code and manifests — including bundled node_modules and webview HTML, where a payload actually hides — get scanned for dense clusters of invisible Unicode (the GlassWorm encoding). The cluster test tolerates a few visible characters between the invisible ones, so sprinkling one visible char between them doesn't defeat it, and it ignores the zero-width joiners and presentation selectors that make up ordinary emoji so a family emoji isn't mistaken for a payload. It also flags unterminated bidirectional controls — the Trojan Source shape — at low severity, while staying quiet on balanced controls and the directional characters that legitimately appear in right-to-left text.
- Find each editor's extensions directory under your home folder, read its
extensions.jsonindex, and union in any extension directory on disk the index doesn't list (a leftover version is exactly where a persistent payload survives an update). - For each extension, hit
open-vsx.org/api/{namespace}and/{namespace}/{name}— lookups run concurrently and are cached per run. - With
--drift, POST the MarketplaceextensionqueryAPI and read the publisher'sisDomainVerifiedflag. - Walk each extension's code and manifests (
.js/.cjs/.mjs/.ts/.json/.html/.md/.map, includingnode_modules) and scan for invisible-Unicode clusters and bidi overrides. - Roll it up by severity;
--jsonfor the structured version,--exit-codefor CI (2 = incomplete, 1 = findings).
- It's Open VSX-centric, because that's the registry the AI-IDE forks actually resolve against. The Marketplace is only consulted with
--drift. - The hidden-payload scan catches encoded invisible-Unicode runs, not every possible obfuscation. It's a strong signal for the GlassWorm class, not a general deobfuscator.
- It flags supply-chain and code-hiding signals, not runtime behavior — it's not a sandbox. A clean report means nothing suspicious on these axes, not that an extension is safe.
- Recommended-extension audits (
vsxprint .) check the registry only; there are no files on disk yet to scan for payloads.
MIT
Chaosuu — t.me/chaosuudev. More tools at github.com/NotChaosuu.