In this article
Cómo defenderse de Glassworm: el malware invisible que está reescribiendo la seguridad de la cadena de suministro
En octubre de 2025, investigadores de seguridad de Koi Security descubrieron Glassworm, el primer gusano autorreplicante dirigido a extensiones de VS Code que utiliza caracteres Unicode invisibles para ocultar código malicioso a plena vista. Con más de 35,800 instalaciones comprometidas y una infraestructura C2 que aún sigue activa, Glassworm representa un nuevo paso en la evolución de los ataques a la cadena de suministro que pone en jaque nuestro modelo tradicional de seguridad para la revisión de código.
Este artículo analiza el ataque Glassworm, rastrea sus orígenes hasta la vulnerabilidad original Trojan Source (CVE-2021-42574) y muestra cómo los desarrolladores y los equipos de seguridad pueden detectar y prevenir estos ataques con caracteres invisibles mediante anti-trojan-source, una herramienta de detección de código abierto para la línea de comandos que ahora incluye análisis de Unicode por categoría y funciones mejoradas para generar informes.
Conclusiones principales:
Los caracteres Unicode invisibles pueden ocultar código malicioso ejecutable que literalmente no pueden ver quienes revisan el código.
La revisión de código tradicional, algunas vistas de diferencias y el resaltado de sintaxis no logran detectar estos ataques.
El ataque se propaga por sí solo mediante credenciales robadas y convierte a cada víctima en un nuevo vector de infección.
Para detectarlo, se necesitan herramientas especializadas que analicen los caracteres Unicode por categoría, no solo mediante listas explícitas.
La evolución de los ataques con caracteres invisibles
Todo comenzó con el acertadamente llamado artículo académico Trojan Source. Sin embargo, para ser precisos sobre la historia de los incidentes de seguridad informática, los caracteres y glifos Unicode ya se habían usado décadas antes para ocultar información y confundir a sistemas y usuarios. En esencia, esta técnica no es nueva.
En noviembre de 2021, los investigadores Nicholas Boucher y Ross Anderson, de la Universidad de Cambridge, publicaron su influyente artículo Trojan Source: Invisible Vulnerabilities, en el que revelaron una vulnerabilidad crítica (CVE-2021-42574) que afecta prácticamente a todos los lenguajes de programación modernos.
El problema fundamental radicaba en los caracteres de control bidireccional (bidi) de Unicode, que pueden reordenar visualmente el código fuente de forma que engañan a quienes lo revisan, aunque se conserva el orden lógico de ejecución que siguen los compiladores e intérpretes.
Considera este código C:
bool isAdmin = false;
/*
begin admins only
*/ if (isAdmin) {
printf("You are an admin.\n");
/*
end admins only
*/ }Lo que ves arriba parece código protegido por una verificación de acceso. Sin embargo, los caracteres de control Unicode ocultos pueden hacer que un comentario parezca código y, por lo tanto, alterar por completo el flujo lógico. El compilador ve una cosa; quien revisa el código ve otra.
Técnicas de ataque:
Retornos anticipados: hacen que una instrucción `return` parezca estar dentro de un comentario.
Comentar código: hacen que los comentarios parezcan código ejecutable.
Cadenas extendidas: hacen que los literales de cadena parezcan código.
Homoglifos: usa caracteres visualmente idénticos de distintos sistemas de escritura (CVE-2021-42694)
El efecto multiplicador en la cadena de suministro
Los investigadores de Trojan Source identificaron un efecto multiplicador crítico en el desarrollo de software moderno:
«Si un adversario logra introducir vulnerabilidades dirigidas en código de código abierto al engañar a quienes lo revisan, es probable que el software derivado herede la vulnerabilidad».
Eso es precisamente lo que vemos hoy en los ataques reales.
Glassworm: la siguiente evolución (2025)
Cuatro años después de que se divulgara Trojan Source, los atacantes han llevado estas técnicas al mundo real. En octubre de 2025, apareció Glassworm, el primer gusano autorreplicante que usa caracteres Unicode invisibles para comprometer extensiones de VS Code en el marketplace OpenVSX.
Qué hace diferente a Glassworm:
Inyección de código invisible: utiliza selectores de variación Unicode que producen cero salida visual. No está ofuscado ni minimizado: literalmente es invisible al ojo humano.
Autorreplicante: roba credenciales de NPM, GitHub y OpenVSX para comprometer automáticamente otros paquetes y extensiones, lo que genera un crecimiento exponencial.
Infraestructura imposible de eliminar:
La blockchain de Solana como C2 principal (inmutable, no se puede desactivar).
Google Calendar como C2 de respaldo (servicio legítimo que elude los controles de seguridad).
Conexiones IP directas con cifrado dinámico.
Capacidades RAT completas: convierte las estaciones de trabajo de desarrolladores infectadas en infraestructura delictiva con proxies SOCKS, servidores VNC ocultos y canales de comando P2P.
Cómo funciona el ataque Glassworm
Etapa 1: la carga útil invisible
Al analizar la extensión CodeJoy para VS Code comprometida (versión 1.8.3), los investigadores de seguridad encontraron lo que parecían ser líneas vacías entre la línea 2 y la línea 7:
import * as vscode from 'vscode';
// [MASSIVE GAP HERE - APPEARS EMPTY]
export function activate(context: vscode.ExtensionContext) {Pero ese espacio no estaba vacío. Contenía código JavaScript ejecutable codificado en caracteres Unicode no imprimibles, específicamente selectores de variación Unicode que no generan ninguna representación visual.
Para un desarrollador que revisa el código: líneas en blanco o espacios en blanco.
Para las herramientas de análisis estático: nada que analizar.
Para el intérprete de JavaScript: código malicioso ejecutable.
Etapa 2: C2 basado en blockchain
El código invisible se conecta a la blockchain de Solana y lee las notas de transacciones de una dirección de cartera codificada de forma fija. ¿Qué hay dentro de la nota? Un objeto JSON con un enlace codificado en base64 para descargar la siguiente etapa:
{"link":"aHR0cDovLzIxNy42OS4zLjIxOC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlTdg=="}Decodificado: http://217.69.3.218/qQD%2FJoi3WCWSk8ggGHiSv%3D%3D
Ten en cuenta que las transacciones de blockchain son inmutables y no se pueden eliminar ni modificar. Las carteras de criptomonedas son seudónimas: no hay ningún proveedor de alojamiento ni registrador involucrado, y los atacantes pueden publicar nuevas transacciones con nuevas URL para las cargas útiles a un costo mínimo.
Etapa 3: robo de credenciales
La carga útil descargada (cifrada con AES-256-CBC, con claves enviadas en los encabezados de respuesta HTTP) busca las siguientes credenciales confidenciales:
Tokens de autenticación de NPM: para publicar paquetes maliciosos.
Tokens de GitHub: para comprometer repositorios.
Credenciales de OpenVSX: para inyectar más extensiones.
Credenciales de Git: para insertar código malicioso.
49 extensiones de carteras de criptomonedas distintas: MetaMask, Phantom, Coinbase, etc.
Pero hay más: un C2 de respaldo que usa Google Calendar. El malware obtiene un evento del calendario que incluye una URL codificada en base64 en el título:
aHR0cDovLzIxNy42OS4zLjIxOC9nZXRfem9tYmlfcGF5bG9hZC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlUdg==Observa la ruta: /get_zombi_payload/. Sí, «zombi», como en una botnet zombi.
La siguiente y última etapa se llama ZOMBI: el RAT completo (troyano de acceso remoto). La carga útil final convierte las estaciones de trabajo de los desarrolladores infectadas en nodos de una red delictiva. Utiliza un proxy SOCKS para enrutar el tráfico dentro de las redes corporativas y otros mecanismos de red, como WebRTC, BitTorrent y VNC oculto.
El atacante puede usar tu navegador y tus sesiones iniciadas, acceder a tu correo y Slack, leer tu código fuente, robar otras credenciales y moverse a otros sistemas de tu red, mientras tú no ves que ocurre nada en tu pantalla.
Cómo detectar caracteres invisibles con anti-trojan-source
El proyecto anti-trojan-source es una herramienta de seguridad de código abierto diseñada específicamente para detectar ataques basados en Unicode, incluida la inyección de caracteres invisibles como la de Glassworm. Creada originalmente para detectar ataques Trojan Source, ha evolucionado para ofrecer protección integral mediante la detección de caracteres específicos y el análisis de Unicode por categoría.
Qué detecta anti-trojan-source
277 caracteres confusos explícitos:
Controles bidireccionales de Unicode (U+202A-U+202E, U+2066-U+2069).
Caracteres de ancho cero (U+200B, U+200C, U+200D).
Selectores de variación (U+FE00-U+FE0F): los 16 selectores básicos.
Selectores de variación extendidos (U+E0100-U+E01EF): 240 caracteres adicionales que usa Glassworm.
Espacio de no separación (U+00A0).
Conector de palabras, guion discrecional y otros caracteres invisibles.
Detección por categoría (a prueba de futuro):
Todos los caracteres de formato Unicode (categoría Cf): detecta los caracteres de formato invisibles por categoría.
Todos los caracteres de control Unicode (categoría Cc), excepto los espacios en blanco de uso común (TAB, LF, CR).
Este enfoque por categoría permite que anti-trojan-source detecte nuevos caracteres invisibles que se agreguen a Unicode en el futuro, sin necesidad de actualizar explícitamente la lista de caracteres.
Funciones de detección
1. Detección booleana simple
import { hasConfusables } from 'anti-trojan-source'
const code = readFileSync('suspicious-file.js', 'utf-8')
const isDangerous = hasConfusables({ sourceText: code })
if (isDangerous) {
console.error('⚠️ Invisible characters detected!')
}2. Hallazgos detallados
import { hasConfusables } from 'anti-trojan-source'
const findings = hasConfusables({
sourceText: code,
detailed: true
})
findings.forEach(finding => {
console.log(`Line ${finding.line}:${finding.column}`)
console.log(` Character: ${finding.codePoint} ${finding.name}`)
console.log(` Category: ${finding.category}`)
console.log(` Context: ${finding.snippet}`)
})Ejemplo de salida:
Line 3:45
Character: U+E0100 VARIATION SELECTOR-17
Category: Variation Selector
Context: const value = getUserInput()
Line 12:8
Character: U+200B ZERO WIDTH SPACE
Category: Cf (Format)
Context: if (isAdmin) {3. Ejemplo de uso de la CLI con varios modos de salida
# Simple mode (exit code 1 if found)
npx anti-trojan-source --files='src/**/*.js'
# Verbose mode with detailed information
npx anti-trojan-source --files='src/**/*.js' --verbose
# JSON mode for programmatic processing
npx anti-trojan-source --files='src/**/*.js' --jsonGuía de implementación de anti-trojan-source
Instala la herramienta de línea de comandos en tu proyecto:
1npm install -D anti-trojan-sourceO úsala directamente con npx, sin necesidad de instalarla:
npx anti-trojan-source --files='**/*.js'Luego, analiza un solo archivo:
npx anti-trojan-source src/index.jsO analiza varios archivos con patrones glob de shell:
npx anti-trojan-source --files='src/**/*.{js,ts,jsx,tsx}'La CLI de anti-trojan-source admite el modo verbose para mostrar resultados detallados. Es útil cuando necesitas saber exactamente qué se detectó y dónde:
npx anti-trojan-source --files='src/**/*.js' --verboseEjemplo de salida:
[x] Detected cases of trojan source in the following files:
|
- src/utils.js
Line 12:34 - U+200B ZERO WIDTH SPACE [Cf (Format)]
Snippet: const value = getUserInput()
Line 45:10 - U+202E RIGHT-TO-LEFT OVERRIDE [Cf (Format)]
Snippet: if (isAdmin) { // Check permissions
Line 78:22 - U+E0100 VARIATION SELECTOR-17 [Variation Selector]
Snippet: const token = process.env.API_KEYTambién admite el modo de salida JSON, que permite el análisis mediante programación. Es útil para la automatización, la integración con CI/CD y la generación de informes personalizados:
npx anti-trojan-source --files='src/**/*.js' --jsonEjemplo de salida:
[
{
"file": "src/utils.js",
"findings": [
{
"line": 12,
"column": 34,
"codePoint": "U+200B",
"name": "ZERO WIDTH SPACE",
"category": "Cf (Format)",
"snippet": "const value = getUserInput()"
},
{
"line": 45,
"column": 10,
"codePoint": "U+202E",
"name": "RIGHT-TO-LEFT OVERRIDE",
"category": "Cf (Format)",
"snippet": "if (isAdmin) { // Check permissions"
}
]
}
]Integración con CI/CD
A continuación, encontrarás un ejemplo para crear una GitHub Action e incluirla en tu proceso de CI:
Crea el archivo .github/workflows/security-scan.yml:
name: Unicode Security Scan
on:
pull_request:
push:
branches: [main, master, develop]
jobs:
scan-invisible-chars:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Scan for invisible Unicode attacks
run: npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx,py,java,go,rs}' --json
- name: Fail on detection
if: failure()
run: |
echo "::error::Invisible Unicode characters detected in source code"
echo "::error::This could indicate a Trojan Source or Glassworm-style attack"
exit 1Detección avanzada por categoría
Cómo entender las categorías Unicode
anti-trojan-source no solo busca caracteres específicos, sino que también los analiza según su categoría Unicode, lo que la prepara para detectar nuevos ataques.
Categoría de formato (Cf):
Caracteres que afectan el formato, pero no generan una salida visual.
Incluye controles bidireccionales, separadores invisibles y caracteres de ancho cero.
Ejemplos: U+200B (ESPACIO DE ANCHO CERO), U+202E (ANULACIÓN DE DERECHA A IZQUIERDA).
Categoría de control (Cc):
Caracteres de control no imprimibles.
Rangos: U+0000-U+001F y U+007F-U+009F.
Excepción: TAB (U+0009), LF (U+000A) y CR (U+000D) están en una lista de permitidos porque son legítimos.
¿Por qué es importante la detección por categoría?
Un enfoque tradicional sería similar al siguiente código JavaScript:
const dangerousChars = ['\u200B', '\u200C', '\u200D', /* ... explicit list */];
// Problem: New Unicode versions add new characters
// Problem: Attackers can use unlisted charactersEn cambio, un enfoque por categoría ofrece estas ventajas:
Detecta TODOS los caracteres de formato (Cf), incluso los que no aparecen en la lista explícita.
Detección de TODOS los caracteres de control (Cc), excepto los espacios en blanco permitidos
Qué hacer ahora
No confíes únicamente en la revisión visual del código, aunque algunos IDE, como VS Code, y el visor de diferencias en línea de GitHub ya permiten indicar algunos caracteres Unicode en el texto. No te fíes de que «todo parece estar bien» ni supongas que tus herramientas muestran automáticamente los caracteres invisibles; en cambio, analízalos de forma proactiva.
Te recomendamos analizar todo el código antes de integrar pull requests. Intégrate con Snyk (es gratis), que ejecuta automáticamente análisis estático de código y monitorea continuamente tu proyecto desde repositorios vinculados a GitHub y otras integraciones.
Usa la herramienta de línea de comandos anti-trojan-source para detectar específicamente caracteres potencialmente peligrosos en tu código fuente.
¿Quieres preparar tu flujo de desarrollo para la próxima evolución de las amenazas a la cadena de suministro? Descarga el documento: Cómo navegar la cadena de suministro de software moderna: proteger el código abierto y el código generado por IA, y cumplir con SBOM.
Recursos
Herramientas:
CLI de anti-trojan-source: https://github.com/lirantal/anti-trojan-source
Complemento de ESLint: https://github.com/lirantal/eslint-plugin-anti-trojan-source
Investigación:
Artículo Trojan Source: https://trojansource.codes/trojan-source.pdf
Análisis de Glassworm: https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace
CVE-2021-42574: divulgación de la vulnerabilidad Trojan Source
CVE-2021-42694: ataques con homoglifos
Estándares:
Estándar Unicode: https://unicode.org/standard/standard.html
Categorías Unicode: https://www.unicode.org/reports/tr44/#General_Category_Values
Protege tu cadena de suministro con Snyk
Los problemas de seguridad de la cadena de suministro afectaron al 87 % de las personas encuestadas. Protege la tuya con Snyk.