La extensión oficial de Claude Code troyanizada: un backdoor SSH distribuido por el marketplace de Cursor
El 29 de octubre el marketplace de Cursor sirvió una versión troyanizada de la extensión oficial de Claude Code que plantaba una clave SSH, abría un túnel inverso y sembraba persistencia. El análisis, etapa por etapa.
El 29 de octubre de 2026, durante unas once horas, el marketplace de extensiones de Cursor sirvió una versión troyanizada de la extensión oficial de Claude Code para Visual Studio Code. No era una imitación ni un proyecto falso compartido en un foro: era el paquete real, con su binario oficial dentro, al que le habían pegado unas decenas de líneas de JavaScript delante. Al cargar la extensión, ese código plantaba una clave SSH pública del atacante en tu directorio personal, intentaba encender el servicio SSH de la máquina, abría un túnel SSH inverso hacia un servidor ajeno y sembraba la persistencia para sobrevivir a un reinicio. Cursor estima que hasta 5.000 personas pudieron instalar el paquete.
La historia tiene un giro que la convierte en un caso de manual, y no es el titular. Dos de los cuatro pasos del ataque fallaban en la mayoría de los equipos: encender el servicio SSH sin permisos de administrador y, en Windows, instalar el servicio de OpenSSH. El túnel se abría, la clave se plantaba, la persistencia se instalaba y el reconocimiento se enviaba. Pero el canal de entrada —la sesión de shell interactiva que el atacante buscaba— normalmente quedaba cerrado. La clave nunca ha estado en el hecho de que el código tuviera errores: está en que el código estaba ahí, esperando a que alguien encendiera el acceso remoto en esa misma máquina.
Lo que sigue es el análisis del implante, etapa por etapa, con los indicadores de compromiso que publicaron Cursor y las dos empresas de seguridad que lo desensamblaron. Al final está la lista de comprobaciones para un equipo que instalara la extensión en la ventana afectada.
Un marketplace dentro de otro marketplace
Cursor es un editor de código basado en Visual Studio Code que mantiene su propio marketplace de extensiones. Por eso hay dos capas de confianza distintas en este caso, y conviene separarlas antes de nada.
La primera es que la extensión era auténtica: `anthropic.claude-code` es la extensión oficial que publica Anthropic, la que conecta el editor con el asistente. La segunda es que el canal de distribución no era el de Anthropic: era el marketplace propio de Cursor, que reempaqueta y sirve las extensiones. El ataque no se produjo en Anthropic ni en el registro público de VS Code, sino en el punto donde Cursor servía su copia.
Ese matiz importa porque las extensiones de un editor se ejecutan con los permisos del propio editor y del usuario que lo ha abierto. Una extensión maliciosa no necesita explotar ninguna vulnerabilidad: hereda el acceso a los ficheros locales, al código fuente del proyecto, a las credenciales de Git guardadas, a las variables de entorno del proceso y a las herramientas de línea de comandos del desarrollador. En cuanto el módulo se carga, el código tiene lo que necesita. Phorion lo resume así: una extensión con backdoor obtiene acceso inmediato a los ficheros locales, al código fuente y a las herramientas del desarrollador en cuanto se carga su módulo.
El otro punto de contexto es el valor de la posición. Un portátil de desarrollo con un asistente de IA instalado es, para un atacante, uno de los equipos con mayor concentración de material útil por equipo: repositorios enteros, tokens de API, credenciales de nube en el entorno y acceso a los sistemas de integración continua. Por eso el objetivo de esta campaña no era robar una clave de un vistazo, sino instalar una vía de acceso permanente y paciente.
El cargador: unas decenas de líneas delante del bundle legítimo
OpenSourceMalware describe el artefacto con una precisión que merece la pena citar: el directorio desempaquetado es idéntico al de la versión normal, y el binario incluido también es el oficial. Lo único diferente es el fichero `extension.js`.
En ese fichero, la primera línea es una función auto-invocada que importa los módulos estándar de Node.js (`child_process`, `fs`, `os`, `path`, `http` y `crypto`) y que termina en la línea 66. A partir de ahí empieza el bundle legítimo de la extensión, con sus exportaciones habituales de `activate` y `deactivate` y con su interfaz de usuario. La colocación no es casual: la función se ejecuta en cuanto Node carga el módulo, antes incluso de que corra el `activate` de la extensión. El usuario no tiene que abrir una conversación con Claude ni tocar nada. Instalar y tener abierto el editor ya es suficiente.
Hay un detalle sobre el modelo de ejecución que conviene retener: el paquete no declara scripts de `install`, `build`, `prepare`, `preinstall` ni `postinstall`. El comportamiento malicioso no depende de ningún gancho del gestor de paquetes, sino del propio runtime de la extensión. En otras palabras, la instalación se ve normal porque lo normal es lo que se ve.
OpenSourceMalware subraya también que casi todas las excepciones están suprimidas. Cuando la plantación de la clave falla, cuando el servicio SSH no arranca, cuando no se puede escribir el agente de arranque, el error se captura y se continúa. El resultado es una extensión que se comporta con normalidad y cuyo fallo no se manifiesta como un fallo.
Etapa 1: plantar la llave y encender el SSH
El primer paso es asegurar que el directorio `~/.ssh` existe, le asigna permisos `0700` y añade a `~/.ssh/authorized_keys` una clave Ed25519 controlada por el atacante, con el comentario `svc-telemetry`. El archivo de claves recibe permisos `0600`. La huella de esa clave es `SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk`.
El script es prudente a la hora de no duplicarse: antes de añadirla comprueba si la cadena `svc-telemetry` ya está presente en el archivo. Phorion señala algo útil para la investigación: comprueba el comentario, no la clave en sí, así que conviene buscar por huella criptográfica y no solo por la etiqueta.
Con la clave dentro de `authorized_keys`, cualquiera que posea la parte privada puede autenticarse como ese usuario, siempre que el servicio SSH esté escuchando. El siguiente paso es, por tanto, encenderlo, y aquí el implante tiene tres ramas: en Linux usa `systemctl` sobre `sshd` o `ssh` y cae a `service` y `/etc/init.d/`; en macOS consulta el estado de Remote Login y, si está apagado, intenta `sudo -n systemsetup -setremotelogin on`; en Windows usa PowerShell para instalar y arrancar OpenSSH Server. Ninguna de las tres incluye una escalada de privilegios: son intentos de usar permisos que ya se tienen o de pedir privilegios de forma no interactiva.
La técnica es simple y su efecto es amplio: una llave SSH authorized_keys es el mecanismo estándar para dar acceso remoto persistente a una máquina sin gestionar claves de servidor. El implante no necesita un agente de shell: le basta con una línea en un archivo de texto que el usuario lleva años manteniendo.
Etapa 2: el túnel inverso y por qué hay dos llaves
La etapa más sistemática es la que establece el canal de control. El implante incluye una clave privada OpenSSH Ed25519 sin cifrar, en base64, con el comentario `svc-telemetry-tun`, y la decodifica en un archivo temporal cuyo nombre sigue el patrón `.stk_<10 hex>`, donde el sufijo son los diez primeros caracteres del hash MD5 del nombre del equipo concatenado a la ruta del directorio personal.
Hay que tener claras las dos llaves porque cumplen papeles opuestos y se confunden con facilidad:
- La clave `svc-telemetry` es pública y se planta en `authorized_keys`. Es la que permite al atacante entrar en tu máquina.
- La clave `svc-telemetry-tun` es privada, va incrustada en el implante y se escribe en el disco de la víctima. Es la que permite a tu máquina conectar hacia el servidor del atacante.
La conexión que se establece es un túnel SSH inverso en el puerto 22 contra `134.209.82.225`, con el usuario `tunnel`, y la opción clave es `-R '*:0:localhost:22'`. El `0` es la parte interesante: pide al servidor SSH remoto que asigne dinámicamente un puerto de escucha y que reenvíe todo lo que llegue a ese puerto de vuelta, a través del túnel, hasta el puerto 22 de la máquina local. El túnel corre con `-N`, es decir, sin ejecutar ningún comando remoto: su único trabajo es mantener abierto el reenvío.
El resultado es una topología de dos conexiones. Tu máquina abre una sesión SSH saliente al servidor del atacante y mantiene allí un puerto publicado. Cuando el operador quiere entrar, abre una segunda sesión SSH separada contra ese puerto asignado y se autentica contra el SSH local de la víctima con la llave plantada en la etapa 1. ofrece un shell interactivo completo y transferencia de ficheros, sin una sola línea de código de comando y control en el JavaScript: el túnel se encarga de todo y el operador usa SSH estándar.
Para que ese túnel sea silencioso y resistente, el comando incluye `StrictHostKeyChecking=no`, `UserKnownHostsFile=/dev/null`, `ServerAliveInterval=30`, `ServerAliveCountMax=3`, `ExitOnForwardFailure=yes` e `IdentitiesOnly=yes`. Si `autossh` está instalado, se usa en su lugar; si no, `ssh` a secas. El proceso se lanza desacoplado, con un bucle que reintenta cada 60 segundos y que usa `pgrep` para no apilar túneles duplicados. Al estar desacoplado, el túnel sobrevive al cierre del editor: si cierras Cursor, la conexión sigue abierta.
Etapa 3: la persistencia
El tercer paso es lo que convierte un acceso puntual en acceso permanente, y aquí el implante se adapta a cada sistema operativo.
En macOS escribe `~/.config/.smt` y `~/.config/.smtk`, y un `LaunchAgent` de usuario en `~/Library/LaunchAgents/com.apple.sysmond.plist` con `RunAtLoad` y `KeepAlive` activados, cargado mediante `launchctl load -w`. El nombre del agente imita la nomenclatura de Apple (`com.apple.sysmond`) y el script ejecutado es `/bin/sh ~/.config/.smt`. Conviene subrayar dos cosas: no es ningún componente de Apple, y corre en el contexto del usuario, no como `root`, aunque se cargue desde `~/Library/LaunchAgents`.
En Linux escribe los mismos dos archivos en `~/.config` y añade una entrada `@reboot` al crontab del usuario que ejecuta `/bin/sh "$HOME/.config/.smt"`. Phorion documenta además un defecto en la comprobación del crontab que, en ejecuciones repetidas, puede borrar tareas cron que ya pertenecían al usuario. Es un efecto colateral del propio implante, y conviene revisarlo al limpiar.
En Windows escribe `.config\smt.cmd` y `.config\smtk`, intenta crear una tarea programada llamada `SysMon` que se ejecuta al arrancar con el usuario `SYSTEM` y, si no puede confirmar que la tarea existe, deja el script `sysmon.cmd` en la carpeta `Startup` del perfil, que se ejecuta al iniciar sesión.
El detalle operativo que más importa aquí es que la persistencia vive fuera del paquete. Son archivos sueltos en el directorio de configuración del usuario, en la carpeta de agentes de lanzamiento, en el crontab o en el registro de tareas programadas. Desinstalar la extensión no elimina ninguno de ellos. Cursor lo dice sin rodeos en su aviso: desinstalar la extensión puede no ser suficiente, porque la carga útil deja scripts de persistencia independientes que siguen ejecutándose.
Etapa 4: el reconocimiento era un inventario, no un volcado
La cuarta etapa recopila información del equipo y la envía una sola vez a un servidor de control. Lo que recoge:
- Nombre del equipo, nombre de usuario, sistema operativo y arquitectura del procesador.
- Ruta del directorio personal y directorio de trabajo actual.
- El correo electrónico global de Git, obtenido con `git config --global user.email`.
- La dirección IP pública, consultando `ifconfig.me` y, como alternativa, `api.ipify.org`.
- Hasta 40 nombres de variables de entorno que coincidan con el patrón `KEY|TOKEN|SECRET|PASS|AWS|GCP`.
Ese último punto merece una pausa, porque es la diferencia entre un volcado de credenciales y un inventario. El código toma `Object.keys(process.env)`, filtra por ese patrón y corta a los 40 primeros. Solo se envía la lista de nombres; los valores no viajan en el beacon. No es descuido del atacante, sino diseño: el objetivo es saber qué tipo de credenciales tiene esa máquina para decidir a cuáles atacar después. Un listado de "aquí hay `AWS_SECRET_ACCESS_KEY` y `GITHUB_TOKEN`" ya vale para decidir por dónde entrar, y una sesión de shell con la llave plantada da acceso directo a esos valores de todas formas.
El beacon se envía 1,5 segundos después de la instalación inicial, como un POST HTTP en claro a `134.209.82.225:8099/beacon` y también a `127.0.0.1:8099/beacon`, en formato JSON. El script no levanta ningún servidor local en ese puerto de loopback, así que esa segunda llamada no tiene destinatario; OpenSourceMalware la describe como un comportamiento sin explicación clara. La respuesta del servidor se descarta sin leer.
Y aquí viene una conclusión que cierra el círculo sobre el alcance: el implante no descarga malware adicional. No hay una segunda etapa, no hay un dropper, no hay código que se ejecute a partir de la respuesta del servidor. Todo lo que se ejecuta se genera o decodifica a partir del contenido que ya venía dentro de la extensión. Las únicas peticiones de red fuera del túnel son las consultas de IP pública, los dos beacons y la propia sesión SSH.
Los dos fallos que salvaron a la mayoría
Ningún análisis serio responsable de este caso puede ignorar dos bugs, porque son la razón por la que el impacto real fue bastante menor que el potencial.
El de macOS. El script consulta el estado del acceso remoto con `systemsetup -getremotelogin` sin privilegios de administrador, y solo intenta encenderlo si la salida coincide con la expresión `/Off/i`. El problema es qué hace `systemsetup` cuando lo ejecuta un usuario normal: imprime `You need administrator access to run this tool... exiting!` y termina con código de salida 0. La salida no contiene la cadena `Off`, así que la condición es falsa y el script se salta por completo la activación. En la práctica, el implante interpreta «no tengo permiso para comprobar si SSH está encendido» como «SSH ya está encendido».
La consecuencia es que, en un Mac donde el acceso remoto no estuviera ya activo, el túnel de salida sí se abría, pero el tráfico que el operador enviara por el puerto reenviado se encontraría con el puerto 22 local cerrado. Como señala Phorion, esto explica además por qué no aparece el comando de activación en la telemetría de procesos: sencillamente nunca se ejecutó.
El de Windows. El código de Windows pasa el comando de PowerShell que instala y arranca OpenSSH Server por una función auxiliar que tiene `/bin/sh` escrito dentro. En una instalación estándar de Windows ese camino no lleva a ningún sitio. Resultado: la rama de Windows tampoco conseguía encender el servicio.
Ahora, la parte que hay que decir con claridad, porque es donde estos análisis se tuercen con facilidad: que el fallo del atacante no es una defensa. La llave quedó plantada igualmente. La persistencia quedó instalada igualmente. El reconocimiento se envió igualmente. El túnel de salida quedó abierto igualmente. Lo único que no funcionó fue la puerta de entrada por shell interactivo. Si el usuario ya tenía el acceso remoto activado, o si alguien lo activa más tarde, o si el equipo tiene una configuración de OpenSSH en la que el servicio ya escuchaba, el backdoor estaba listo y esperando. La propia nota de Cursor pone el listón en el sitio correcto: ante lo inusual de que Cursor se hubiera iniciado con privilegios de administrador, los errores del código malicioso habrían hecho fallar ese paso; fuera de ahí, los pasos fallan, pero todo lo demás se instala.
Lo que hizo Cursor
La respuesta de Cursor fue rápida y, en términos de higiene, bastante sólida. Al detectar el paquete lo retiró de inmediato y bloqueó la distribución posterior. Después, por prudencia, apagó el marketplace entero, auditó los accesos y confirmó que ninguna otra extensión había sido manipulada, rotó sus claves, reconstruyó el marketplace y lo volvió a levantar. En paralelo, se puso en contacto con el proveedor de alojamiento del servidor del atacante para desactivar `134.209.82.225`, con lo que el túnel y el envío de datos de host dejaron de funcionar.
Cursor también publicó en su aviso la lista completa de indicadores de compromiso y dos scripts de remediación, uno para macOS y otro para Windows, que eliminan la extensión, la persistencia, los procesos del túnel y la llave del atacante, y que salen con estado distinto de cero si queda algo en pie. La empresa dijo que contactaría directamente con las personas afectadas y estimó el alcance en 5.000 usuarios.
El indicador operativo más útil que sale del aviso es la comprobación de la llave plantada. En macOS:
`grep -qs svc-telemetry ~/.ssh/authorized_keys && echo "svc-telemetry key found" || echo "No key found; machine not impacted"`
En Windows, en PowerShell, hay que mirar tanto el archivo del perfil como `C:\ProgramData\ssh\administrators_authorized_keys`. Si la llave está, el equipo está afectado.
Las tres cosas que todavía no están resueltas
Un caso bien documentado deja siempre alguna pregunta abierta, y aquí hay tres que conviene no dar por resueltas.
De dónde salió la versión maliciosa. El aviso de Cursor no explica el vector de entrada, y nadie ha identificado a los autores. Lo que sí se ha hecho es acotar el perímetro: Cursor auditó el acceso a su marketplace y afirma que ninguna otra extensión fue alterada. OpenSourceMalware señala que la procedencia del directorio analizado no está establecida, y que lo que demuestra el análisis es que ese artefacto concreto es malicioso, no que la infraestructura oficial de Anthropic estuviera comprometida.
De dónde salió el número de versión. Aquí está el detalle más interesante, y lo más incómodo. En las primeras publicaciones se señaló a Open VSX como origen del paquete, y la nota de Phorion sigue diciendo eso. Pero OpenSourceMalware corrigió su artículo después de que Cursor les contactara: el origen era el marketplace de Cursor, no Open VSX. Comprobando los registros, no existe una versión 2.1.313 ni en Open VSX ni en el marketplace de Microsoft ni en npm para el paquete `@anthropic-ai/claude-code`; la versión buena que se publicó al día siguiente fue la 2.1.314. Y hay una comprobación que cualquiera puede repetir: el changelog oficial de Claude Code salta de la 2.1.312 a la 2.1.314. No hay entrada 2.1.313. Es decir, ese número de versión nunca existió oficialmente: lo inventó el atacante. La lección es sencilla y no necesita más evidencia: un número de versión que parece superior al último que tienes no significa que sea legítimo, y el origen de la instalación importa más que el número.
Si hay clasificación formal. No se ha asignado CVE, no hay puntuación CVSS y no aparece en el catálogo de vulnerabilidades explotadas conocidas de CISA. La razón es conceptual: esto no es un fallo de Claude Code, es un artefacto malicioso publicado en un canal de distribución comprometido. Un CVE no es el instrumento adecuado para describir ese tipo de evento. Rescana lo clasifica como severidad alta en su evaluación, y en ese marco el impacto se sitúa donde corresponde: un compromiso de cadena de suministro en una herramienta de desarrollo, con acceso remoto persistente, aunque el vector de entrada se quedara cerrado en la mayoría de los casos.
Anthropic no ha publicado un comunicado sobre el incidente, ni en su página de noticias, ni en su página de estado, ni en los avisos de seguridad del repositorio de Claude Code.
Qué hacer si instalaste Claude Code en Cursor el 29 de octubre
La ventana de exposición va de las 09:27 a las 20:35 UTC del 29 de octubre de 2026, y las versiones afectadas son la 2.1.312 y la 2.1.313 del marketplace de Cursor para macOS y Windows. La versión correcta es la 2.1.314 o posterior, y conviene comprobar de dónde salió exactamente la que tienes instalada, porque el número por sí solo no dice nada.
La lista de comprobación que se desprende de los tres análisis publicados:
- Confirma la versión instalada y el origen. Que sea 2.1.314 o superior y que venga del marketplace oficial, no de una copia reempaquetada.
- Busca la llave plantada con el comando de arriba, tanto en el perfil del usuario como en `C:\ProgramData\ssh\administrators_authorized_keys`.
- Ejecuta el script de remediación de Cursor para tu plataforma, o haz sus pasos a mano: mata los procesos del bucle de reintento y los `ssh` o `autossh` que conecten con `134.209.82.225`; descarga y borra el `LaunchAgent` `com.apple.sysmond.plist` en macOS, la entrada `@reboot` del crontab en Linux o la tarea `SysMon` y el script de `Startup` en Windows; borra `~/.config/.smt` y `~/.config/.smtk`; elimina la llave `svc-telemetry` de `authorized_keys` y los archivos `.stk_*` del directorio temporal.
- Comprueba que el acceso remoto por SSH no se ha quedado activado sin que nadie lo hiciera. Es el punto donde el fallo del atacante podría producir un daño colateral en la dirección contraria.
- Revisa los registros de SSH buscando sesiones entrantes que aparezcan como procedentes de `localhost` o `::1` autenticadas con la llave `svc-telemetry`. Esa es la firma de un acceso real, y Phorion la señala como la forma de distinguir un túnel simplemente establecido de un túnel que alguien ha utilizado.
- Rota las credenciales y las sesiones accesibles desde ese equipo, desde un sistema limpio. El hecho de que el beacon solo enviara nombres de variables de entorno no protege de nada a propósito: si alguien entró por el túnel, el shell le daba acceso directo a los valores.
- Bloquea `134.209.82.225` en los puertos 22 y 8099 en el control de la aplicación, y recoge la evidencia antes de limpiar. OpenSourceMalware es explícito en el orden: si el artefacto llegó a cargarse, hay que desconectar el sistema, preservar la evidencia forense y retirar la extensión y la persistencia después.
- Revisa la crontab del usuario en Linux antes de nada. El defecto en la comprobación puede haber borrado tareas que tu usuario tenía programadas.
Lo que deja el caso
Hay un detalle de construcción en este implante que merece más atención que el número de víctimas. El archivo `extension.js` de la extensión legítima, con su binario oficial y su interfaz intacta, precedido por una función de 66 líneas. No hubo que romper nada, ni parchear un binario, ni construir un código ofuscado. Hubo que poner unas líneas delante.
Ese es el patrón que se está instalando en el ecosistema de herramientas de IA, y conviene mirarlo de frente: el asistente de IA que instalas en tu editor es una extensión con acceso privilegiado a tu código, tus credenciales y tu red. Que sea legítima no la hace más segura que cualquier otra extensión, y que su código sea de una empresa conocida no protege frente a un compromiso del canal de distribución.
Lo segundo que deja el caso es una lección sobre el análisis de incidentes que va más allá de esta campaña. Los dos fallos del atacante explican por qué el impacto fue menor, y publicarlos con precisión es lo correcto. Pero un informe que se queda ahí puede transmitir una sensación de seguridad que no se sostiene: la llave plantada, la persistencia, el reconocimiento y el túnel saliente se instalaron en todos los casos, Independientemente de que la puerta de entrada funcionara. El backdoor no estaba fallando: estaba esperando.
Y lo tercero es la lección más práctica. La verificación de la llave de `authorized_keys` es una orden de una línea, tarda segundos y no requiere ningún producto de seguridad. Cuando una extensión de tu editor instala acceso remoto persistente en tu máquina, esa comprobación va antes que cualquier análisis de complejidad. El resto es teatro.
Fuentes
- Cursor — Security notice: malicious version of Anthropic.claude-code (2.1.312 and 2.1.313) identified in the Cursor VS Code Extension Marketplace
- Phorion — Official Claude Code Extension Compromise: Security Advisory
- OpenSourceMalware — Trojanized Claude Code Extension Plants an SSH Backdoor
- Sophos X-Ops — Malicious Claude Code extension distributed through Cursor marketplace
- Rescana — Trojanized Claude Code Extension 2.1.313 in Cursor Marketplace Plants SSH Backdoor
- The Hacker News — Official Claude Code Extension on Cursor Marketplace Trojanized with Persistent SSH Backdoor
- Changelog oficial de Claude Code (anthropics/claude-code)
- Cursor — Script de remediación para macOS
- Cursor — Script de remediación para Windows