Back to News Feed
GlassWorm Hits React Native — Two Popular npm Packages Backdoored with Multi-Stage Credential Stealer via Solana Blockchain C2
Malware 2026-03-18

GlassWorm Hits React Native — Two Popular npm Packages Backdoored with Multi-Stage Credential Stealer via Solana Blockchain C2

The GlassWorm campaign has compromised two widely-used React Native npm packages — react-native-country-select@0.3.91 and react-native-international-phone-number@0.11.8 — injecting a multi-stage Windows credential and cryptocurrency stealer that uses the Solana blockchain as its primary C2 channel. Over 134,000 monthly downloads were in the blast radius. The attack executes on a simple `npm install` with zero visual indicators — no malicious code is visible in any editor or code review tool.

supply-chainnpmreact-nativeglasswormcredential-stealercrypto-stealersolana-blockchaininvisible-unicodepreinstall-hookdeveloper-securityVS-CodeZOMBI-RATCI-CDopen-source-security

On March 16, 2026, security researchers at Aikido detected that two popular React Native npm packages had been silently backdoored with a multi-stage credential and cryptocurrency stealer. The compromised packages — published by the same developer account AstrOOnauta — had a combined 134,887 monthly downloads at the time of compromise.

The attack is attributed to the GlassWorm campaign, a self-propagating supply chain worm first identified by Koi Security in October 2025 that has since compromised hundreds of npm packages, VS Code extensions, and GitHub repositories. This latest wave represents GlassWorm's most impactful compromise to date — infecting packages used in production mobile applications worldwide.

Affected Packages

PackageMalicious VersionWeekly DownloadsStatus
react-native-country-select0.3.91~15,000⚠️ Compromised
react-native-international-phone-number0.11.8~19,000⚠️ Compromised

If you installed either of these exact versions, your machine should be treated as fully compromised. See the remediation section below.

How the Attack Works

The infection chain is triggered by a single npm install command. No user interaction is required beyond the routine package installation that every developer performs daily.

Stage 0: Preinstall Hook Injection

The attacker modified the package.json of both packages to include a preinstall script — a lifecycle hook that npm executes before the package installation begins:

TERMINAL_CODE
{
  "scripts": {
    "preinstall": "node install.js"
  }
}

This hook runs the obfuscated install.js payload before any dependency resolution or lock file validation occurs. The execution is:

  • Silent — no output to the terminal
  • Automatic — triggered on npm install, npm ci, or any dependency resolution
  • Universal — affects developer machines, CI runners, build agents, and Docker builds

Stage 1: Locale Check + Solana C2 Resolution

The install.js script performs two critical operations:

1. Russian Locale Detection (Kill Switch)

TERMINAL_CODE
// Simplified — actual code uses heavy string array shuffling + RC4 obfuscation
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

if (locale.startsWith('ru') || timezone.includes('Moscow')) {
    process.exit(0); // Cease execution on Russian systems
}

This is a well-known tactic among Russian-speaking cybercriminal groups to avoid prosecution by domestic law enforcement. If the system locale contains ru_RU, ru-RU, or Russian timezone identifiers, the malware terminates silently without deploying any payload.

2. Solana Blockchain C2 Query

If the locale check passes, the script queries the Solana public blockchain for a specific wallet address hardcoded in the payload. The attacker embeds base64-encoded C2 URLs in the memo field of Solana transactions:

TERMINAL_CODE
Solana Blockchain (Immutable Ledger)
        │
        ├── Transaction Memo: "aHR0cHM6Ly80NS4zMi4xNTAuMjUxL3N0YWdlMi..." (base64)
        │                      ↓ decode
        │                      https://45.32.150.251/stage2/payload.enc
        │
        └── The malware reads the latest memo from this wallet
            to get the current C2 URL — rotated daily

This design makes the C2 infrastructure virtually impossible to take down:

  • Blockchain transactions are immutable — they cannot be deleted or modified
  • The Solana network is decentralized — there is no single server to seize
  • The wallet address is public — but blocking access to the entire Solana RPC network is impractical

Stage 2: Encrypted Payload Download

The base64-decoded URL points to an encrypted second-stage payload hosted on attacker-controlled infrastructure. This stage:

  • Downloads a decryption key
  • Unlocks the Stage 3 binary
  • Validates the target environment (Windows-only execution path)

Stage 3: Credential & Crypto Stealer

The final stage deploys a persistent Windows credential stealer with the following capabilities:

CapabilityDetails
Crypto wallet theftTargets 49+ browser-based wallet extensions (MetaMask, Phantom, Coinbase Wallet, etc.), Ledger, and Trezor hardware wallet data
Developer credential theftHarvests npm tokens, GitHub tokens, Git credentials, SSH keys, OpenVSX tokens
Browser data extractionCookies, saved passwords, autofill data from Chrome, Brave, Edge, Firefox
PersistenceWindows Task Scheduler + Run registry key
Backup C2Google Calendar events containing base64-encoded payload URLs as backup relay
Node.js runtimeDownloads its own Node.js binary to execute JavaScript payloads independent of the developer's environment

Full Kill Chain Diagram

TERMINAL_CODE
Developer runs: npm install
        │
        ▼
package.json → "preinstall": "node install.js"
        │
        ▼
install.js checks system locale
        │
        ├── Russian locale detected → EXIT (kill switch)
        │
        └── Non-Russian locale → Continue
                │
                ▼
        Query Solana blockchain wallet for latest C2 URL
                │
                ▼
        Decode base64 memo → Get encrypted payload URL
                │
                ▼
        Download Stage 2 (decryption keys)
                │
                ▼
        Download Stage 3 (credential stealer)
                │
                ▼
        Persist via Task Scheduler + Registry
                │
                ▼
        Exfiltrate: npm tokens, GitHub tokens, SSH keys,
        crypto wallets, browser data → attacker C2
                │
                ▼
        Stolen credentials used to compromise MORE packages
        (Self-propagating worm behavior)

The GlassWorm Campaign: History and Evolution

GlassWorm is not a one-off attack. It is a self-propagating worm that has been systematically compromising the developer ecosystem since late 2025.

Timeline

DateEvent
October 2025Koi Security discovers GlassWorm targeting VS Code extensions — first public disclosure
November 2025GlassWorm found using invisible Unicode characters to hide payloads in npm packages
December 2025Campaign expands to OpenVSX marketplace — 14+ extensions compromised
January 2026Palo Alto Networks acquires Koi Security for its GlassWorm detection capabilities
February 2026GlassWorm evolves to replace Unicode obfuscation with compiled Rust binaries in some variants
March 2026Hundreds of GitHub repositories compromised via stolen tokens; React Native packages backdoored

What Makes GlassWorm Unique

1. Invisible Unicode Obfuscation

GlassWorm's signature technique uses Unicode Private Use Area (PUA) characters in the ranges 0xFE00–0xFE0F and 0xE0100–0xE01EF. These characters render as zero-width invisible whitespace in every major code editor, terminal, and code review interface.

TERMINAL_CODE
// What you see in your editor:
const x = "                    ";

// What actually exists (hex):
// 0xFE00 0xFE01 0xFE02 ... (encoded malicious payload)

// What the runtime executes:
eval(decodePUA(x)); // → Full credential stealer

This means a developer can review every line of code in a compromised package and see nothing suspicious. The malicious payload exists in what appears to be empty whitespace. Standard grep, diff, and code review tools do not flag it.

2. Blockchain-Based C2 (Takedown-Resistant)

Traditional malware C2 servers can be seized by law enforcement. GlassWorm embeds its C2 instructions in immutable Solana blockchain transactions, making takedowns structurally impossible without shutting down the entire blockchain network.

3. Google Calendar Backup C2

As a fallback, GlassWorm uses public Google Calendar events with base64-encoded URLs in the event title. Google Calendar is a trusted service — it is not blocked by corporate firewalls, web proxies, or DNS filters.

4. Self-Propagation

Once GlassWorm steals npm tokens and GitHub credentials from a developer's machine, it uses those credentials to:

  • Publish malicious versions of packages the developer maintains
  • Force-push commits containing invisible Unicode payloads to GitHub repos
  • Upload compromised VS Code extensions to the marketplace

This creates an exponential infection cascade — each compromised developer potentially infects every package and repo they have write access to.

5. ZOMBI RAT Module

On fully compromised systems, GlassWorm deploys a hidden VNC server called ZOMBI that provides complete remote desktop access, plus a SOCKS proxy that turns the developer's machine into criminal relay infrastructure.

Indicators of Compromise

Malicious Package Versions

TERMINAL_CODE
Compromised npm Packages (March 2026):
  - react-native-country-select@0.3.91
  - react-native-international-phone-number@0.11.8

Previously Compromised npm Packages (2025-2026):
  - os-info-checker-es6
  - skip-tot
  - solana-pump-test
  - solana-spl-sdk
  - "@kodane/patch-manager"

C2 IP Addresses (Active February–March 2026)

TERMINAL_CODE
Stage 3 Payload Servers:
  - 45.32.150.251
  - 217.69.3.152

Solana C2 Relay IPs (rotated):
  - 45.32.150.97     # Active February 2026
  - 217.69.11.57     # Active February 2026
  - 217.69.11.99     # Active February–March 2026
  - 217.69.0.159     # Active March 13, 2026
  - 45.76.44.240     # Active March 13, 2026

Compromised VS Code / OpenVSX Extensions

TERMINAL_CODE
Known Compromised Extensions (with malicious versions):
  - codejoy.codejoy-vscode-extension (v1.8.3, v1.8.4)
  - JScearcy.rust-doc-viewer (v4.2.1)
  - sissel.shopify-liquid (v4.0.1)
  - ginfuru.better-nunjucks (v0.3.2)
  - CodeInKlingon.git-worktree-menu (v1.0.9, v1.0.91)
  - cline-ai-main.cline-ai-agent (v3.1.3)  # Microsoft VSCode Marketplace
  # ... 14+ additional OpenVSX extensions

File System Artifacts

TERMINAL_CODE
Windows Persistence:
  - Task Scheduler entry with randomized name executing Node.js binary
  - Registry: HKCU\Software\Microsoft\Windows\CurrentVersion\Run
  - Dropped Node.js runtime in %APPDATA% or %TEMP%

macOS Persistence:
  - ~/Library/LaunchAgents/ (plist files with obfuscated names)

Detection

Audit Your Lock Files Immediately

TERMINAL_CODE
# Check if compromised versions are in your project
grep -r "react-native-country-select" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null | grep "0.3.91"
grep -r "react-native-international-phone-number" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null | grep "0.11.8"

# Check for preinstall hooks in all dependencies (suspicious if unexpected)
find node_modules -name "package.json" -exec grep -l '"preinstall"' {} \;

Detect Invisible Unicode in Your Codebase

TERMINAL_CODE
# Scan for invisible Private Use Area characters (GlassWorm signature)
grep -rP '[\x{FE00}-\x{FE0F}]' --include="*.js" --include="*.ts" .
grep -rP '[\x{E0100}-\x{E01EF}]' --include="*.js" --include="*.ts" .

# Alternative: use file entropy scanning
find . -name "*.js" -exec sh -c '
    entropy=$(cat "$1" | wc -c)
    visible=$(cat "$1" | tr -cd "[:print:]" | wc -c)
    ratio=$(echo "scale=2; $visible/$entropy" | bc 2>/dev/null)
    if [ "$(echo "$ratio < 0.90" | bc 2>/dev/null)" = "1" ]; then
        echo "SUSPICIOUS: $1 (visible ratio: $ratio)"
    fi
' _ {} \;

Network IOC Detection (SIEM / Firewall)

TERMINAL_CODE
// Azure Sentinel / Defender XDR — detect outbound connections to known C2 IPs
DeviceNetworkEvents
| where TimeGenerated > ago(30d)
| where RemoteIP in (
    "45.32.150.251", "217.69.3.152",
    "45.32.150.97", "217.69.11.57",
    "217.69.11.99", "217.69.0.159", "45.76.44.240"
)
| project TimeGenerated, DeviceName, InitiatingProcessFileName,
    RemoteIP, RemotePort, RemoteUrl
| order by TimeGenerated desc
TERMINAL_CODE
// Detect Solana RPC queries from developer workstations (anomalous)
DeviceNetworkEvents
| where TimeGenerated > ago(14d)
| where RemoteUrl has_any ("api.mainnet-beta.solana.com", "solana-api.projectserum.com")
    or RemoteUrl has "solana" and RemotePort == 443
| where InitiatingProcessFileName in~ ("node.exe", "node", "npm", "npx")
| project TimeGenerated, DeviceName, InitiatingProcessFileName,
    RemoteUrl, InitiatingProcessCommandLine

Windows Persistence Detection

TERMINAL_CODE
# Check for suspicious Task Scheduler entries
Get-ScheduledTask | Where-Object {
    $_.Actions.Execute -like "*node*" -or
    $_.Actions.Arguments -like "*install*"
} | Select-Object TaskName, TaskPath, State, @{N='Command';E={$_.Actions.Execute + " " + $_.Actions.Arguments}}

# Check for suspicious Run registry entries
Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" |
    ForEach-Object { $_.PSObject.Properties | Where-Object { $_.Value -like "*node*" -or $_.Value -like "*appdata*" } }

YARA Rule

TERMINAL_CODE
rule GlassWorm_NPM_Supply_Chain {
    meta:
        description = "Detects GlassWorm supply chain infection indicators in npm packages"
        author = "4nuxd.one"
        date = "2026-03-18"
        reference = "GlassWorm Campaign - React Native npm Backdoor"
    strings:
        // Invisible Unicode PUA characters (GlassWorm signature)
        $unicode1 = { FE 00 }
        $unicode2 = { FE 0F }

        // Solana C2 patterns
        $sol1 = "mainnet-beta.solana.com" ascii
        $sol2 = "getSignaturesForAddress" ascii
        $sol3 = "getTransaction" ascii

        // Google Calendar backup C2
        $gcal1 = "calendar.google.com" ascii
        $gcal2 = "calendar/ical" ascii

        // Preinstall hook abuse
        $hook1 = "\"preinstall\"" ascii
        $hook2 = "install.js" ascii

        // Credential harvesting targets
        $cred1 = ".npmrc" ascii
        $cred2 = "npm_token" ascii nocase
        $cred3 = "GITHUB_TOKEN" ascii
        $cred4 = "gh_token" ascii nocase

        // Known C2 IPs
        $ip1 = "45.32.150.251" ascii
        $ip2 = "217.69.3.152" ascii
        $ip3 = "45.32.150.97" ascii

        // ZOMBI RAT indicators
        $zombi1 = "ZOMBI" ascii
        $zombi2 = "vnc" ascii nocase
        $zombi3 = "socks" ascii nocase

    condition:
        (2 of ($unicode*)) or
        (2 of ($sol*)) or
        ($hook1 and $hook2 and 1 of ($cred*)) or
        (2 of ($ip*)) or
        (1 of ($sol*) and 1 of ($cred*)) or
        ($zombi1 and 1 of ($zombi2, $zombi3))
}

Immediate Remediation

If you installed either compromised package version, treat the machine as fully compromised and execute the following:

1. Credential Rotation (Do This First)

TERMINAL_CODE
ROTATE IMMEDIATELY:
  npm:
    - Revoke all npm access tokens: https://www.npmjs.com/settings/tokens
    - Generate new tokens with minimal scope
    - Enable npm 2FA if not already active

  GitHub:
    - Revoke all GitHub Personal Access Tokens
    - Revoke all GitHub OAuth app authorizations
    - Review GitHub Actions secrets for any repos you maintain
    - Enable GitHub secret scanning alerts

  SSH:
    - Regenerate all SSH key pairs
    - Remove old public keys from authorized_hosts on all servers

  Cryptocurrency:
    - Transfer ALL funds from any wallet accessible on the compromised machine
    - Generate new wallet addresses from a CLEAN device
    - Revoke any dApp approvals from compromised wallets

  CI/CD:
    - Rotate all CI/CD secrets (GitHub Actions, GitLab CI, CircleCI, etc.)
    - Invalidate all service account tokens
    - Review recent CI/CD runs for unauthorized builds

2. System Forensics

TERMINAL_CODE
# Check for the compromised preinstall hook
cat node_modules/react-native-country-select/package.json | grep -A2 preinstall
cat node_modules/react-native-international-phone-number/package.json | grep -A2 preinstall

# Check outbound connections to known C2 infrastructure
# Linux/macOS
netstat -an | grep -E "45\.32\.150\.|217\.69\."
ss -tnp | grep -E "45\.32\.150\.|217\.69\."

# Check for dropped Node.js binaries (persistence mechanism)
find /tmp ~/.local ~/.config -name "node" -type f 2>/dev/null
find "$APPDATA" "$TEMP" -name "node.exe" 2>nul  # Windows

3. Pin and Audit Dependencies

TERMINAL_CODE
# Pin to known-safe versions
npm install react-native-country-select@0.3.9  # Pre-compromise version
npm install react-native-international-phone-number@0.11.7  # Pre-compromise version

# Use npm audit signatures to verify package integrity
npm audit signatures

# Enable package provenance verification
npm config set //registry.npmjs.org/:enforce-provenance=true

Hardening: Protecting Your Supply Chain

1. Disable Preinstall Hooks Globally

The most impactful single control against this entire attack class:

TERMINAL_CODE
# Ignore all lifecycle scripts during installation
npm config set ignore-scripts true

# Run scripts only after manual review
npm install --ignore-scripts
npm rebuild  # Explicitly run build scripts after audit

2. Use Lockfile-Only Installation in CI/CD

TERMINAL_CODE
# GitHub Actions — enforce frozen lockfile
- name: Install dependencies
  run: npm ci --ignore-scripts
  # npm ci uses package-lock.json exactly, rejecting any modifications

3. Monitor for Invisible Characters

Add a pre-commit hook to detect invisible Unicode in your codebase:

TERMINAL_CODE
#!/bin/bash
# .git/hooks/pre-commit — detect invisible Unicode injection

if git diff --cached --diff-filter=ACM -z -- '*.js' '*.ts' '*.json' |
   xargs -0 grep -Pn '[\x{FE00}-\x{FE0F}\x{E0100}-\x{E01EF}]' 2>/dev/null; then
    echo "ERROR: Invisible Unicode characters detected — possible GlassWorm injection"
    echo "Review the flagged files before committing"
    exit 1
fi

4. Implement npm Provenance Verification

TERMINAL_CODE
# Require all packages to have verified provenance (Sigstore-based)
npm config set //registry.npmjs.org/:enforce-provenance=true

# Check provenance status of a specific package
npm audit signatures --package react-native-country-select

5. Network-Level Controls

ControlConfiguration
Block known C2 IPsAdd 45.32.150.251, 217.69.3.152, and related IPs to firewall deny lists
Monitor Solana RPC trafficAlert on any developer workstation or CI runner connecting to Solana blockchain endpoints
DNS sinkholeRedirect suspicious domains to internal sinkhole for detection
Egress filteringDeveloper machines should not have unrestricted outbound internet access — enforce proxy with TLS inspection

Why This Matters Beyond React Native

This attack isn't just about two React Native packages. It's a demonstration of a systemic vulnerability in the npm ecosystem — and by extension, every package registry that supports lifecycle hooks.

The Trust Problem

npm packages are installed by millions of developers who trust the registry and the package maintainers. GlassWorm weaponizes that trust by:

  1. Compromising maintainer accounts (via stolen credentials from previous infections)
  2. Publishing malicious versions that are indistinguishable from legitimate updates
  3. Using those new infections to steal more credentials and repeat the cycle

This is a worm — each infection creates the conditions for the next. The attack surface expands with every compromised developer.

The Visibility Problem

Invisible Unicode obfuscation defeats every standard code review practice:

  • git diff shows nothing
  • GitHub PR reviews show nothing
  • VS Code syntax highlighting shows nothing
  • grep finds nothing (unless explicitly configured for PUA ranges)

The malicious code is structurally invisible to human reviewers and most automated tools.

The Takedown Problem

With C2 embedded in the Solana blockchain, the attacker's infrastructure is:

  • Immutable — cannot be removed
  • Decentralized — no single point to seize
  • Public — anyone can read it, but no one can delete it

Traditional law enforcement takedown procedures are ineffective against blockchain-based C2.

Key Takeaways

  • Two popular React Native packages were backdoored via preinstall hook abuse. react-native-country-select@0.3.91 and react-native-international-phone-number@0.11.8 deploy a multi-stage credential stealer on npm install.
  • GlassWorm is a self-propagating worm, not a one-off attack. Stolen credentials are used to compromise additional packages, creating an exponential infection cascade across the npm ecosystem.
  • Solana blockchain C2 is takedown-resistant. The attacker's command infrastructure is embedded in immutable blockchain transactions — there is no server to seize.
  • Invisible Unicode characters defeat code review. The malicious payload is encoded in zero-width characters that are invisible in every editor, terminal, and review interface.
  • npm install is a code execution event. Lifecycle hooks (preinstall, postinstall) run arbitrary code with the developer's full system permissions. Disable them by default with --ignore-scripts.
  • Any machine that installed the compromised versions is fully compromised. Rotate all npm tokens, GitHub tokens, SSH keys, and cryptocurrency wallets immediately — from a clean device.

The compromised package versions have been reported and action is being taken by the npm security team. Developers using these packages should immediately audit their lock files and follow the remediation steps above regardless of whether they believe they installed the malicious versions — dependency resolution may have pulled them in transitively.

Tags

#SUPPLY-CHAIN#NPM#REACT-NATIVE#GLASSWORM#CREDENTIAL-STEALER#CRYPTO-STEALER#SOLANA-BLOCKCHAIN#INVISIBLE-UNICODE#PREINSTALL-HOOK#DEVELOPER-SECURITY#VS-CODE#ZOMBI-RAT#CI-CD#OPEN-SOURCE-SECURITY
Disseminate_Intel: