SEVEN.LEGEND // V4
Users Online: 1
Total Hits: 8,853
CODES // DATA STREAM
SECURITY CODE & SCRIPTS « BACK
GlassWorm VS Code Extension Scanner - Detect & Remove Malicious Extensions PS
POWERSHELL
Powershell code to detect and remove GlassWorm malware from VS Code extensions. Detects 14 known infected extensions, invisible Unicode code, Solana blockchain C2, credential theft patterns, and system compromise. Auto-removes infected extensions with --remove flag. Works on Windows.
ID: glassworm-vscode-scanner-ps // LANG: Powershell // LINES: 489
#Requires -Version 5.1
<#
.SYNOPSIS
    GlassWorm VS Code Extension Scanner
    
.DESCRIPTION
    Detects and removes malicious VS Code extensions infected by the GlassWorm worm.
    Based on Koi Security research (October 2025).
    
.PARAMETER Remove
    Automatically remove infected extensions
    
.PARAMETER SystemCheck
    Perform system-level compromise checks
    
.PARAMETER ReportPath
    Path to save JSON report
    
.EXAMPLE
    .\GlassWorm-Scanner.ps1
    
.EXAMPLE
    .\GlassWorm-Scanner.ps1 -Remove -SystemCheck
    
.LINK
    https://www.koi.ai/blog/glassworm-first-self-propagating-worm
#>

[CmdletBinding()]
param(
    [switch]$Remove,
    [switch]$SystemCheck,
    [string]$ReportPath = "glassworm_scan_report.json"
)

# Known infected extensions
$InfectedExtensions = @{
    "codejoy.codejoy-vscode-extension" = @("1.8.3", "1.8.4")
    "l-igh-t.vscode-theme-seti-folder" = @("1.2.3")
    "kleinesfilmroellchen.serenity-dsl-syntaxhighlight" = @("0.3.2")
    "JScearcy.rust-doc-viewer" = @("4.2.1")
    "SIRILMP.dark-theme-sm" = @("3.11.4")
    "CodeInKlingon.git-worktree-menu" = @("1.0.9", "1.0.91")
    "ginfuru.better-nunjucks" = @("0.3.2")
    "ellacrity.recoil" = @("0.7.4")
    "grrrck.positron-plus-1-e" = @("0.0.71")
    "jeronimoekerdt.color-picker-universal" = @("2.8.91")
    "srcery-colors.srcery-colors" = @("0.3.9")
    "sissel.shopify-liquid" = @("4.0.1")
    "TretinV3.forts-api-extention" = @("0.3.1")
    "cline-ai-main.cline-ai-agent" = @("3.1.3")
}

# Known malicious indicators
$C2Servers = @(
    "217.69.3.218",
    "199.247.10.166",
    "140.82.52.31"
)

$SolanaWallet = "28PKnu7RzizxBzFPoLp69HLXp9bJL3JFtT2s5QzHsEA2"

# Unicode variation selectors (invisible characters)
$VariationSelectors = @(
    [char]0xFE00, [char]0xFE01, [char]0xFE02, [char]0xFE03,
    [char]0xFE04, [char]0xFE05, [char]0xFE06, [char]0xFE07,
    [char]0xFE08, [char]0xFE09, [char]0xFE0A, [char]0xFE0B,
    [char]0xFE0C, [char]0xFE0D, [char]0xFE0E, [char]0xFE0F
)

$Findings = @()
$RemovedExtensions = @()

function Get-VSCodeExtensionsPath {
    $paths = @()
    
    $userProfile = $env:USERPROFILE
    $paths += Join-Path $userProfile ".vscode\extensions"
    $paths += Join-Path $userProfile ".vscode-insiders\extensions"
    
    return $paths | Where-Object { Test-Path $_ }
}

function Get-InstalledExtensions {
    $extensions = @()
    
    foreach ($extDir in Get-VSCodeExtensionsPath) {
        $folders = Get-ChildItem -Path $extDir -Directory
        
        foreach ($folder in $folders) {
            # Parse extension ID and version from folder name
            $dirName = $folder.Name
            
            if ($dirName -match '^(.+)-(\d+\.\d+\.\d+.*)$') {
                $extId = $Matches[1]
                $version = $Matches[2]
            } else {
                $extId = $dirName
                $version = "unknown"
            }
            
            # Try to get accurate info from package.json
            $packageJson = Join-Path $folder.FullName "package.json"
            
            if (Test-Path $packageJson) {
                try {
                    $package = Get-Content $packageJson -Raw | ConvertFrom-Json
                    $publisher = $package.publisher
                    $name = $package.name
                    $version = $package.version
                    
                    if ($publisher -and $name) {
                        $extId = "$publisher.$name"
                    }
                } catch {
                    # Continue with parsed values
                }
            }
            
            $extensions += [PSCustomObject]@{
                Id = $extId
                Version = $version
                Path = $folder.FullName
            }
        }
    }
    
    return $extensions
}

function Test-InvisibleUnicode {
    param([string]$FilePath)
    
    $foundSelectors = @()
    
    try {
        $content = Get-Content $FilePath -Raw -ErrorAction SilentlyContinue
        
        foreach ($selector in $VariationSelectors) {
            if ($content -match [regex]::Escape($selector)) {
                $foundSelectors += "U+{0:X4}" -f [int][char]$selector
            }
        }
    } catch {
        # Ignore errors
    }
    
    return $foundSelectors
}

function Test-MaliciousPatterns {
    param([string]$ExtensionPath)
    
    $indicators = @{
        C2Servers = @()
        SolanaReferences = $false
        InvisibleUnicode = @()
        SuspiciousFiles = @()
        GoogleCalendarC2 = $false
        CredentialTheft = $false
    }
    
    # Search through JS files
    $jsFiles = Get-ChildItem -Path $ExtensionPath -Filter "*.js" -Recurse -ErrorAction SilentlyContinue
    
    foreach ($file in $jsFiles) {
        try {
            $content = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue
            
            # Check for C2 servers
            foreach ($c2 in $C2Servers) {
                if ($content -match [regex]::Escape($c2)) {
                    $indicators.C2Servers += $c2
                }
            }
            
            # Check for Solana wallet
            if ($content -match [regex]::Escape($SolanaWallet)) {
                $indicators.SolanaReferences = $true
            }
            
            # Check for invisible Unicode
            $unicodeFound = Test-InvisibleUnicode -FilePath $file.FullName
            if ($unicodeFound) {
                $indicators.InvisibleUnicode += $unicodeFound
                $indicators.SuspiciousFiles += $file.FullName
            }
            
            # Check for Google Calendar C2
            if ($content -match 'calendar\.google\.com|googleapis\.com/calendar') {
                $indicators.GoogleCalendarC2 = $true
            }
            
            # Check for credential theft
            $credentialKeywords = @('npm', 'token', 'github', 'git credential', '.npmrc', 'vsix')
            foreach ($keyword in $credentialKeywords) {
                if ($content -match [regex]::Escape($keyword)) {
                    $indicators.CredentialTheft = $true
                    break
                }
            }
        } catch {
            # Ignore errors
        }
    }
    
    return $indicators
}

function Remove-InfectedExtension {
    param(
        [string]$ExtensionPath,
        [string]$ExtensionId
    )
    
    try {
        Write-Host "   🗑️  Removing extension: $ExtensionId" -ForegroundColor Yellow
        Remove-Item -Path $ExtensionPath -Recurse -Force
        $script:RemovedExtensions += $ExtensionId
        Write-Host "   ✅ Successfully removed!" -ForegroundColor Green
    } catch {
        Write-Host "   ❌ Failed to remove: $_" -ForegroundColor Red
    }
    Write-Host ""
}

function Start-ExtensionScan {
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host "GlassWorm VS Code Extension Scanner" -ForegroundColor Cyan
    Write-Host "Detecting malicious extensions from October 2025 supply chain attack" -ForegroundColor Cyan
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host ""
    
    $extensions = Get-InstalledExtensions
    
    if ($extensions.Count -eq 0) {
        Write-Host "❌ No VS Code extensions found!" -ForegroundColor Red
        return
    }
    
    Write-Host "📦 Found $($extensions.Count) installed extensions" -ForegroundColor White
    Write-Host ""
    
    $infectedCount = 0
    $suspiciousCount = 0
    
    foreach ($ext in $extensions) {
        $isKnownInfected = $false
        
        # Check if it's a known infected extension
        if ($InfectedExtensions.ContainsKey($ext.Id)) {
            $infectedVersions = $InfectedExtensions[$ext.Id]
            
            if ($infectedVersions -contains $ext.Version -or $ext.Version -eq "unknown") {
                $isKnownInfected = $true
                $infectedCount++
                
                Write-Host "🚨 CRITICAL: Known infected extension found!" -ForegroundColor Red
                Write-Host "   Extension: $($ext.Id)" -ForegroundColor White
                Write-Host "   Version: $($ext.Version)" -ForegroundColor White
                Write-Host "   Location: $($ext.Path)" -ForegroundColor White
                Write-Host ""
                
                $script:Findings += [PSCustomObject]@{
                    Id = $ext.Id
                    Version = $ext.Version
                    Path = $ext.Path
                    Status = "KNOWN_INFECTED"
                    RiskScore = 100
                }
                
                if ($Remove) {
                    Remove-InfectedExtension -ExtensionPath $ext.Path -ExtensionId $ext.Id
                }
            }
        }
        
        # Deep scan for malicious patterns
        if (-not $isKnownInfected) {
            $indicators = Test-MaliciousPatterns -ExtensionPath $ext.Path
            
            $riskScore = 0
            $reasons = @()
            
            if ($indicators.C2Servers.Count -gt 0) {
                $riskScore += 50
                $reasons += "C2 servers found: $($indicators.C2Servers -join ', ')"
            }
            
            if ($indicators.SolanaReferences) {
                $riskScore += 30
                $reasons += "Solana blockchain references"
            }
            
            if ($indicators.InvisibleUnicode.Count -gt 0) {
                $riskScore += 40
                $reasons += "Invisible Unicode characters: $($indicators.InvisibleUnicode | Select-Object -Unique)"
            }
            
            if ($indicators.GoogleCalendarC2) {
                $riskScore += 20
                $reasons += "Google Calendar API usage (potential C2)"
            }
            
            if ($indicators.CredentialTheft) {
                $riskScore += 15
                $reasons += "Credential access patterns"
            }
            
            if ($riskScore -ge 50) {
                $suspiciousCount++
                
                if ($riskScore -ge 80) {
                    Write-Host "🚨 HIGH RISK" -ForegroundColor Red
                } else {
                    Write-Host "⚠️  SUSPICIOUS" -ForegroundColor Yellow
                }
                
                Write-Host "   Extension: $($ext.Id)" -ForegroundColor White
                Write-Host "   Version: $($ext.Version)" -ForegroundColor White
                Write-Host "   Risk Score: $riskScore" -ForegroundColor White
                Write-Host "   Location: $($ext.Path)" -ForegroundColor White
                Write-Host "   Indicators:" -ForegroundColor White
                
                foreach ($reason in $reasons) {
                    Write-Host "      • $reason" -ForegroundColor Gray
                }
                Write-Host ""
                
                $script:Findings += [PSCustomObject]@{
                    Id = $ext.Id
                    Version = $ext.Version
                    Path = $ext.Path
                    Status = "SUSPICIOUS"
                    RiskScore = $riskScore
                    Indicators = $reasons
                }
                
                if ($Remove -and $riskScore -ge 80) {
                    Remove-InfectedExtension -ExtensionPath $ext.Path -ExtensionId $ext.Id
                }
            }
        }
    }
    
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host "📊 SCAN SUMMARY" -ForegroundColor Cyan
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host "Total Extensions Scanned: $($extensions.Count)" -ForegroundColor White
    Write-Host "🚨 Known Infected: $infectedCount" -ForegroundColor Red
    Write-Host "⚠️  Suspicious: $suspiciousCount" -ForegroundColor Yellow
    Write-Host "✅ Clean: $($extensions.Count - $infectedCount - $suspiciousCount)" -ForegroundColor Green
    
    if ($RemovedExtensions.Count -gt 0) {
        Write-Host ""
        Write-Host "🗑️  Removed Extensions: $($RemovedExtensions.Count)" -ForegroundColor Yellow
        foreach ($ext in $RemovedExtensions) {
            Write-Host "   • $ext" -ForegroundColor Gray
        }
    }
}

function Test-SystemCompromise {
    Write-Host ""
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host "🔍 CHECKING FOR SYSTEM COMPROMISE" -ForegroundColor Cyan
    Write-Host ("="*70) -ForegroundColor Cyan
    
    # Check for suspicious network connections
    Write-Host ""
    Write-Host "[1] Checking for suspicious network connections..." -ForegroundColor White
    
    try {
        $connections = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue
        $suspiciousFound = $false
        
        foreach ($c2 in $C2Servers) {
            $found = $connections | Where-Object { $_.RemoteAddress -eq $c2 }
            if ($found) {
                Write-Host "   🚨 SUSPICIOUS: Connection to known C2 server: $c2" -ForegroundColor Red
                $suspiciousFound = $true
            }
        }
        
        if (-not $suspiciousFound) {
            Write-Host "   ✅ No suspicious network connections detected" -ForegroundColor Green
        }
    } catch {
        Write-Host "   ℹ️  Unable to check network connections" -ForegroundColor Yellow
    }
    
    # Check for credential files
    Write-Host ""
    Write-Host "[2] Checking for potential credential theft..." -ForegroundColor White
    
    $sensitiveFiles = @(
        "$env:USERPROFILE\.npmrc",
        "$env:USERPROFILE\.gitconfig",
        "$env:USERPROFILE\.git-credentials"
    )
    
    foreach ($file in $sensitiveFiles) {
        if (Test-Path $file) {
            Write-Host "   ⚠️  Found: $file" -ForegroundColor Yellow
            Write-Host "      Recommendation: Review and rotate credentials" -ForegroundColor Gray
        }
    }
    
    # Check for suspicious processes
    Write-Host ""
    Write-Host "[3] Checking for suspicious processes..." -ForegroundColor White
    
    $suspiciousProcesses = @("node", "python", "powershell") | ForEach-Object {
        Get-Process -Name $_ -ErrorAction SilentlyContinue
    }
    
    if ($suspiciousProcesses) {
        Write-Host "   ℹ️  Found $($suspiciousProcesses.Count) potentially suspicious processes" -ForegroundColor Yellow
        Write-Host "      Review running processes manually for anomalies" -ForegroundColor Gray
    }
}

function Export-ScanReport {
    if ($Findings.Count -gt 0) {
        $Findings | ConvertTo-Json -Depth 10 | Out-File -FilePath $ReportPath -Encoding UTF8
        Write-Host ""
        Write-Host "💾 Detailed report saved to: $ReportPath" -ForegroundColor Green
    }
}

function Show-Recommendations {
    Write-Host ""
    Write-Host ("="*70) -ForegroundColor Cyan
    Write-Host "🛡️  SECURITY RECOMMENDATIONS" -ForegroundColor Cyan
    Write-Host ("="*70) -ForegroundColor Cyan
    
    Write-Host @"

1. IMMEDIATE ACTIONS:
   • Remove all infected extensions immediately
   • Restart VS Code completely
   • Check VS Code extension auto-update settings
   
2. CREDENTIAL SECURITY:
   • Rotate ALL credentials (GitHub, npm, Git, API tokens)
   • Review GitHub OAuth apps and revoke suspicious ones
   • Check npm access tokens: npm token list
   • Review .npmrc and .git-credentials files
   
3. CRYPTOCURRENCY WALLETS:
   • Check all cryptocurrency wallet extensions
   • Review transaction history for unauthorized transfers
   • Move funds to new wallets with new keys
   
4. SYSTEM SECURITY:
   • Scan for SOCKS proxy or VNC servers
   • Monitor network traffic for suspicious connections
   • Check running processes for unusual activity
   • Run Windows Defender or antivirus scan
   
5. PREVENTION:
   • Only install extensions from trusted publishers
   • Disable automatic extension updates
   • Regularly audit installed extensions
   • Use extension allow-lists in enterprise environments
   
6. MONITORING:
   • Watch for unauthorized code commits to repositories
   • Monitor npm package publishing activity
   • Check for unexpected VS Code extension updates
   
For more information:
https://www.koi.ai/blog/glassworm-first-self-propagating-worm

"@ -ForegroundColor White
}

# Main execution
Start-ExtensionScan

if ($SystemCheck) {
    Test-SystemCompromise
}

if ($Findings.Count -gt 0) {
    Export-ScanReport
}

Show-Recommendations