Backdoor su Cursor: fiducia e fork di VS Code
La backdoor nell'estensione Claude Code su Cursor Marketplace mostra perché i fork di VS Code non ereditano la fiducia di Microsoft. Cosa verificare.

Uno sviluppatore apre Cursor un martedì mattina, vede un aggiornamento dell'estensione Claude Code e lo lascia installare. Non sembra cambiare nulla. L'estensione si carica, l'assistente AI funziona e a pranzo la macchina ha una chiave Ed25519 dell'attaccante in ~/.ssh/authorized_keys, un tunnel SSH inverso che invia segnali verso un server su Internet e un LaunchAgent travestito da processo di sistema Apple che mantiene tutto attivo dopo i riavvii. Questa è la backdoor nel Cursor Marketplace divulgata da Phorion, e la dimostrazione più chiara finora di qualcosa di cui noi della sicurezza parliamo da anni: i fork di VS Code ereditano la codebase di Microsoft, ma non ereditano il suo modello di fiducia.
Ho passato buona parte della carriera a fare incident response su macchine di sviluppatori, e questo incidente mi tocca perché il meccanismo di distribuzione era così ordinario. Nessun phishing. Nessuno zero-day. Solo un bundle di estensione manomesso, versioni 2.1.312 e 2.1.313, pubblicato su un marketplace di cui gli sviluppatori si fidano di default perché sembra la cosa di cui già si fidano. I canali ufficiali di Anthropic erano puliti: qualcuno ha usato l'accesso alla pipeline di pubblicazione di Cursor per distribuire una copia modificata di un'estensione che Anthropic non aveva mai compilato per quel registry. Il raggio d'azione erano tutti gli utenti Cursor che hanno fatto l'auto-update in una finestra di undici ore.
Che cosa faceva davvero la backdoor
L'implant era un'espressione di funzione immediatamente invocata anteposta al bundle minificato extension.js. Tutto ciò che seguiva il prefisso iniettato sembrava la normale estensione: stessi export activate e deactivate, stesso codice UI, stesse stringhe. È la parte che dovrebbe preoccuparvi se la vostra strategia di rilevamento è «fare il diff del bundle con l'upstream»: la parte malevola sono poche centinaia di byte di JavaScript extra appoggiati sopra un file legittimo di megabyte. Parte appena Node carica il modulo, prima che si attivi la callback activate dell'estensione. L'utente non deve cliccare nulla, invocare Claude o interagire in alcun modo con l'estensione.
Il payload è un lavoro in quattro fasi, e onestamente è fatto bene:
- Inserimento della chiave. Aggiunge una chiave pubblica Ed25519 controllata dall'attaccante, commentata
svc-telemetry, a~/.ssh/authorized_keyscon i permessi corretti 0700/0600. - Attivazione SSH. Cerca di assicurarsi che sshd sia in esecuzione: systemctl su Linux, Remote Login su macOS, OpenSSH Server tramite PowerShell su Windows.
- Tunnel inverso. Apre una connessione SSH in uscita verso un server controllato dall'operatore con
-R *:0:localhost:22, così l'attaccante può risalire attraverso il tunnel alla porta 22 della vittima e accedere con la chiave inserita. Non serve un protocollo C2 personalizzato: SSH è il C2. - Persistenza e ricognizione. Un LaunchAgent utente chiamato
com.apple.sysmond.plistsu macOS, una voce @reboot di cron su Linux, un fallback nella cartella Esecuzione automatica su Windows. Poi un beacon una tantum con hostname, username, email git, IP pubblico e fino a quaranta nomi di variabili d'ambiente che corrispondono a/KEY|TOKEN|SECRET|PASS|AWS|GCP/i.
L'ultimo punto è intelligente in un modo che vale la pena capire. Il beacon non esfiltra la vostra chiave segreta AWS: esfiltra il fatto che ne avete una. È un inventario per il targeting. L'operatore ottiene un menu di quali macchine compromesse appartengono a ingegneri cloud con credenziali attive nell'ambiente, ed è esattamente a chi tornare per primo attraverso il tunnel.
Il bug di macOS che ha accidentalmente salvato le persone
Ecco il dettaglio che trovo cupamente divertente come incident responder: il percorso di attivazione su macOS del malware è rotto da un classico errore di scripting. Il codice esegue systemsetup -getremotelogin come utente senza privilegi e prova ad attivare Remote Login solo se l'output corrisponde a /Off/i. Ma su macOS quel comando si rifiuta di girare senza accesso da amministratore. Stampa «You need administrator access to run this tool... exiting!» su stdout, e termina con stato 0.
// what the malware effectively does
var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
if (/Off/i.test(rl)) {
det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
}
La stringa di errore non contiene «Off», quindi la regex fallisce, quindi il passo di attivazione non viene mai eseguito. Lo script tratta «non ho i permessi per verificare se SSH è spento» come «SSH è già acceso». Un exit code 0 da un comando fallito è uno dei trabocchetti più antichi dello scripting shell, e stavolta ha colpito l'autore del malware invece di un sysadmin. Su un Mac senza Remote Login già attivo, il tunnel inverso si connette senza problemi, ma tutto ciò che viene inoltrato indietro trova la porta 22 chiusa. Le chiavi e la persistenza vengono comunque installate (la macchina è comunque compromessa), ma il percorso di accesso interattivo è morto in partenza.
Non prendetelo come una consolazione. Se l'utente aveva già Remote Login attivo, o lo attiva in seguito, la chiave inserita resta in authorized_keys in attesa. Il percorso Windows ha un bug simile (fa passare un comando PowerShell attraverso un helper che hardcoda /bin/sh), e la logica cron su Linux può cancellare il crontab esistente di un utente ai run successivi. Il malware scritto da esseri umani ha bug umani. Quello del prossimo mese no.
Perché i marketplace dei fork non possono ereditare il modello di fiducia di Microsoft
Questo è il punto strutturale, e voglio che i responsabili dell'ingegneria ci riflettano. Quando installate un'estensione dal Visual Studio Marketplace ufficiale, c'è una pipeline reale dietro: account editore con verifica del dominio, scansione in fase di pubblicazione, processi di rimozione e un'azienda con un ufficio legale dall'altra parte. Non è perfetta (estensioni malevole ci sono già passate), ma è un sistema.
I fork di VS Code come Cursor non possono usare quel marketplace per motivi di licenza, quindi puntano a registry alternativi: Open VSX o infrastrutture proprie. L'interfaccia dell'editor è identica byte per byte a quella conosciuta. La pagina dell'estensione mostra lo stesso nome, la stessa icona, un nome editore plausibile. Ma ogni controllo che rendeva affidabile il marketplace originale è una reimplementazione, oppure è assente. Chi ha verificato l'editore? Chi ha analizzato il bundle? L'artefatto è stato compilato dal repo sorgente dichiarato, e potete verificarlo? In questo incidente, le versioni manomesse apparentemente non sono mai esistite nel registry di Microsoft: vivevano solo su quello del fork. La fiducia era puramente visiva.
Abbiamo già visto lo stesso film nel mondo dei package registry. La compromissione di LiteLLM su PyPI ha funzionato perché il punto debole è l'accesso alla pubblicazione, non il codice. Le estensioni sono peggio dei pacchetti sotto un aspetto importante: non c'è una cultura dei lockfile, nessuna riproducibilità con npm ci, nessun hash pinning. L'editor si aggiorna in silenzio e, come ha scoperto un commentatore di Hacker News, il ritardo di auto-update di VS Code non si applica nemmeno ai «trusted publisher», una lista che non potete configurare. Il vostro set di estensioni è un albero di dipendenze live e non fissato, che gira con tutti i vostri privilegi utente e si aggiorna da solo mentre dormite.

L'AI per il coding è un'escalation di privilegi che avete scelto voi
Togliete il dramma del marketplace e guardate qual era il bersaglio reale. Un'estensione per il coding AI non è un tema di colori. Gira con i permessi dell'IDE, che sono i vostri permessi, e il suo compito è leggere l'albero dei sorgenti, custodire le vostre credenziali API e, sempre più spesso, eseguire comandi shell per vostro conto. Quando abbiamo scritto di come le CLI di coding agentico gestiscono i permessi, il tema ricorrente era che l'agente utile e quello pericoloso si distinguono per policy, non per capacità. Un'estensione che può eseguire child_process è a una pubblicazione malevola di distanza dall'eseguire qualsiasi cosa.
Un commentatore lo ha detto senza giri di parole: chiunque usi questi strumenti ha di fatto eseguito curl-pipe-bash ogni giorno, e per un decennio abbiamo detto ai junior di non farlo. È un'esagerazione, ma solo di poco. La differenza tra curl | bash e un'estensione agentica con auto-update è che l'estensione ha un'interfaccia più carina. Ho già sostenuto che le CLI non dovrebbero installare daemon in background di default; questo incidente è ciò che succede quando il daemon lo installa qualcun altro e voi non avete mai dato il consenso. Un processo ssh staccato che sopravvive alla chiusura dell'IDE è esattamente il pattern di comportamento che segnalo nelle review, distribuito da un attaccante attraverso un canale di aggiornamento fidato.
Come controllare subito la vostra flotta
Se qualcuno nella vostra organizzazione usa Cursor, trattatelo come un incidente in corso, non come una notizia. Gli indicatori ad alto segnale, direttamente dal write-up forense:
# macOS / Linux: the planted key is the cleanest signal
grep -s svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "key not found"
# Check persistence artifacts
ls ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null # macOS
ls ~/.config/.smt ~/.config/.smtk 2>/dev/null # macOS + Linux
crontab -l 2>/dev/null | grep -F .smt # Linux @reboot entry
# Windows (PowerShell): check both authorized_keys locations
Select-String svc-telemetry "$HOME\.ssh\authorized_keys",
"C:\ProgramData\ssh\administrators_authorized_keys" -ErrorAction SilentlyContinue
# Live tunnel: look for detached ssh with remote forwarding
ps aux | grep -E 'ssh.*-R' | grep -v grep
Alcune note di caccia dall'esperienza. Primo, controllate il fingerprint della chiave, non solo il commento: l'implant deduplica sulla stringa di commento svc-telemetry, e così potrebbe fare un tentativo di pulizia. Secondo, il file della chiave del tunnel si trova nella directory temporanea con un nome del tipo .stk_ seguito da dieci caratteri esadecimali derivati da un MD5 di hostname più percorso della home; è deterministico per macchina, quindi potete calcolare il nome esatto del file e fare la ricerca. Terzo, la telemetria dei processi EDR non mostrerà il tentativo di attivazione su macOS per gli utenti standard, perché a causa del bug non viene mai eseguito. L'assenza di systemsetup -setremotelogin nei log non significa assenza di compromissione. La chiave e il LaunchAgent sono la verità di riferimento.
Se trovate la chiave, la macchina riceve il trattamento completo di IR: ruotate ogni credenziale il cui nome di variabile d'ambiente potrebbe essere stato inviato dal beacon (cioè quasi tutte: la regex cattura AWS, GCP, qualunque cosa con KEY o TOKEN nel nome), revocate l'accesso della macchina e reinstallatela da zero. Non limitatevi a cancellare la chiave e considerare chiuso. L'operatore aveva, o ha tentato di avere, accesso shell interattivo; non potete elencare ciò che ha toccato dal solo codice dell'implant. È anche qui che le zone cieche nel logging dell'autenticazione cloud fanno male: controllate i log di audit cloud per uso di console o API nella finestra tra l'aggiornamento dell'estensione e la divulgazione, filtrando per le credenziali dell'utente interessato.

Hardening: contenete l'editor, non solo l'estensione
Dopo un incidente tutti chiedono cosa cambiare. La mia posizione, e la difendo: non riuscirete mai a fare audit fino a ottenere un ecosistema di estensioni sicuro, quindi dovete contenere. Abbiamo trattato il compromesso generale in contenimento vs monitoraggio per gli agenti AI, e questo incidente è il caso di studio del contenimento. Il monitoraggio qui non vi ha detto niente finché una società di sicurezza esterna non ha dissezionato il bundle.
- Fissate le versioni delle estensioni e disattivate l'auto-update per tutto ciò che ha permessi ampi, e applicate la stessa regola a tutti i fork. Sì, vi ritroverete con la stanchezza degli aggiornamenti. Costa meno della risposta a un incidente.
- Filtraggio dell'egress sulle workstation degli sviluppatori. Tutto il modello di accesso qui dipendeva da una connessione SSH in uscita verso un IP di un VPS. Se le vostre macchine di sviluppo non possono aprire porte 22 in uscita verso destinazioni arbitrarie, la fase del tunnel muore. Quasi nessuno lo fa, e avrebbe neutralizzato completamente questo implant.
- Monitorate lo stato SSH come canarino. Segnalate quando Remote Login si accende, quando compaiono nuove chiavi in authorized_keys e quando ci sono processi sshd su macchine che non dovrebbero eseguirlo.
- Inventariate gli strumenti AI come inventariate i server. Sappiate quali estensioni e CLI di agenti sono approvate, quali permessi hanno e da quale marketplace provengono. Se la risposta a «da quale registry arrivano le installazioni di Cursor nella nostra azienda» è un'alzata di spalle, è il progetto del prossimo trimestre.
- Preferite agenti che girano in sandbox o container dove possibile, con credenziali iniettate per sessione invece che ambientali nell'environment, perché lo scraping dei nomi delle variabili del beacon funziona proprio perché lasciamo segreti in giro negli environment dei processi.
Un marketplace di estensioni senza una propria pipeline di verifica non è un marketplace: è un file host con un pulsante di installazione.
La conclusione scomoda
Anthropic non è stata violata. È stato violato il marketplace di Cursor, e il brand di Anthropic ha assorbito comunque il danno, perché per uno sviluppatore l'estensione è il prodotto, a prescindere da chi ha pubblicato quale build. Questa è l'economia degli ecosistemi forkati: il modo più economico per colpire un bersaglio di alto valore è attaccare il registry più debole di cui quel bersaglio si fida. Gli assistenti per il coding AI stanno nell'intersezione perfetta: base di installazione enorme, massimi privilegi locali, credenziali ovunque e una supply chain che fa fork e rihosta i loro artefatti su registry con postura di sicurezza molto diversa.
Questa volta è finita con le versioni malevole ritirate dopo circa undici ore e un bug di macOS che ha limitato i danni. La risposta è stata adeguata. Il problema è l'architettura, e non è cambiata: il vostro editor è un package manager che esegue tutto ciò che installa come voi, lo aggiorna in silenzio e, in un numero crescente di setup, prende i pacchetti da un registry che nessuno nel vostro team di sicurezza ha mai valutato. Sistemate le regole di egress, fissate le estensioni e inventariate gli agenti. La prossima compromissione di un publisher non troverà un bug nella regex a salvare i vostri utenti.


