In Databricks, il nostro AI Red Team esplora regolarmente come i nuovi paradigmi software possano introdurre rischi di sicurezza imprevisti. Una tendenza recente che abbiamo seguito da vicino è il "vibe coding", l'uso casuale e rapido dell'IA generativa per creare codice. Sebbene questo approccio acceleri lo sviluppo, abbiamo scoperto che può anche introdurre vulnerabilità sottili e pericolose che passano inosservate finché non è troppo tardi.
In questo post, esploriamo alcuni esempi reali dai nostri sforzi di red team, mostrando come il vibe coding possa portare a gravi vulnerabilità. Dimostriamo anche alcune metodologie per le pratiche di prompting che possono aiutare a mitigare questi rischi.
In uno dei nostri esperimenti iniziali sull'esplorazione dei rischi del vibe coding, abbiamo incaricato Claude di creare un'arena di battaglia di serpenti in terza persona, in cui gli utenti avrebbero controllato il serpente da una prospettiva della telecamera dall'alto utilizzando il mouse. Coerentemente con la metodologia del vibe-coding, abbiamo concesso al modello un controllo sostanziale sull'architettura del progetto, sollecitandolo incrementalmente a generare ogni componente. Sebbene l'applicazione risultante abbia funzionato come previsto, questo processo ha inavvertitamente introdotto una vulnerabilità di sicurezza critica che, se non controllata, avrebbe potuto portare all'esecuzione di codice arbitrario.
Il livello di rete del gioco Snake trasmette oggetti Python serializzati e deserializzati utilizzando pickle, un modulo noto per essere vulnerabile all'esecuzione di codice remoto arbitrario (RCE). Di conseguenza, un client o server dannoso potrebbe creare e inviare payload che eseguono codice arbitrario su qualsiasi altra istanza del gioco.
Il codice seguente, tratto direttamente dal codice di rete generato da Claude, illustra chiaramente il problema: gli oggetti ricevuti dalla rete vengono deserializzati direttamente senza alcuna validazione o controllo di sicurezza.
Sebbene questo tipo di vulnerabilità sia classico e ben documentato, la natura del vibe coding rende facile trascurare i potenziali rischi quando il codice generato sembra "funzionare".
Tuttavia, chiedendo a Claude di implementare il codice in modo sicuro, abbiamo osservato che il modello ha identificato e risolto proattivamente i seguenti problemi di sicurezza:
Come mostrato nell'estratto di codice seguente, il problema è stato risolto passando da pickle a JSON per la serializzazione dei dati. È stato inoltre imposto un limite di dimensione per mitigare gli attacchi di negazione del servizio.
In un altro esperimento, abbiamo chiesto a ChatGPT di generare un parser per il formato binario GGUF, ampiamente riconosciuto come difficile da analizzare in modo sicuro. I file GGUF memorizzano i pesi dei modelli per moduli implementati in C e C++, e abbiamo scelto specificamente questo formato poiché Databricks ha precedentemente riscontrato diverse vulnerabilità nella libreria GGUF ufficiale.
ChatGPT ha rapidamente prodotto un'implementazione funzionante che ha gestito correttamente l'analisi dei file e l'estrazione dei metadati, come mostrato nel codice sorgente seguente.
Tuttavia, dopo un esame più attento, abbiamo scoperto significative falle di sicurezza relative alla gestione non sicura della memoria. Il codice C/C++ generato includeva letture di buffer non controllate e istanze di type confusion, entrambi i quali potrebbero portare a vulnerabilità di corruzione della memoria se sfruttati.
In questo parser GGUF, esistono diverse vulnerabilità di corruzione della memoria dovute a input non controllato e aritmetica di puntatori non sicura. I problemi principali includevano:
Un attaccante potrebbe sfruttare il secondo di questi problemi creando un file GGUF con un header fittizio, una lunghezza estremamente grande o negativa per un campo chiave o valore e dati payload arbitrari. Ad esempio, una lunghezza della chiave di 0xFFFFFFFFFFFFFFFF (il valore massimo a 64 bit senza segno) potrebbe causare un malloc() non controllato per restituire un buffer piccolo, ma il successivo memcpy() scriverebbe comunque oltre esso, provocando un classico buffer overflow basato su heap. Allo stesso modo, se il parser assume una lunghezza di stringa o array valida e la legge in memoria senza convalidare lo spazio disponibile, potrebbe trapelare contenuti di memoria. Queste falle potrebbero potenzialmente essere utilizzate per ottenere l'esecuzione di codice arbitrario.
Per convalidare questo problema, abbiamo chiesto a ChatGPT di generare una prova di concetto che crea un file GGUF dannoso e lo passa al parser vulnerabile. L'output risultante mostra il programma che va in crash all'interno della funzione memmove, che sta eseguendo la logica corrispondente alla chiamata memcpy non sicura. Il crash si verifica quando il programma raggiunge la fine di una pagina di memoria mappata e tenta di scrivere oltre essa in una pagina non mappata, attivando un errore di segmentazione a causa di un accesso alla memoria fuori dai limiti.
Ancora una volta, abbiamo chiesto a ChatGPT suggerimenti per correggere il codice ed è stato in grado di suggerire i seguenti miglioramenti:
Abbiamo quindi preso il codice aggiornato e gli abbiamo passato il file GGUF di prova di concetto e il codice ha rilevato il record malformato.
Ancora una volta, il problema principale non era la capacità di ChatGPT di generare codice funzionale, ma piuttosto che l'approccio casuale intrinseco al vibe coding ha permesso che supposizioni pericolose passassero inosservate nell'implementazione generata.
Sebbene non ci sia sostituto per un esperto di sicurezza che esamini il tuo codice per garantire che non sia vulnerabile, diverse strategie pratiche e a basso sforzo possono aiutare a mitigare i rischi durante una sessione di vibe coding. In questa sezione, descriviamo tre metodi semplici che possono ridurre significativamente la probabilità di generare codice insicuro. Ciascuno dei prompt presentati in questo post è stato generato utilizzando ChatGPT, dimostrando che qualsiasi vibe coder può facilmente creare prompt efficaci orientati alla sicurezza senza un'ampia esperienza di sicurezza.
Il primo approccio prevede l'utilizzo di un prompt di sistema generico e focalizzato sulla sicurezza per incoraggiare l'LLM verso comportamenti di codifica sicuri fin dall'inizio. Tali prompt forniscono una guida di sicurezza di base, migliorando potenzialmente la sicurezza del codice generato. Nei nostri esperimenti, abbiamo utilizzato il seguente prompt:
Quando il linguaggio di programmazione o il contesto dell'applicazione sono noti in anticipo, un'altra strategia efficace è fornire all'LLM un prompt di sicurezza personalizzato, specifico per linguaggio o applicazione. Questo metodo mira direttamente a vulnerabilità note o a insidie comuni pertinenti al compito da svolgere. In particolare, non è nemmeno necessario essere consapevoli di queste classi di vulnerabilità esplicitamente, poiché un LLM stesso può generare prompt di sistema adatti. Nei nostri esperimenti, abbiamo istruito ChatGPT a generare prompt specifici per linguaggio utilizzando la seguente richiesta:
Il terzo metodo incorpora un passaggio di revisione auto-riflessiva immediatamente dopo la generazione del codice. Inizialmente, non viene utilizzato alcun prompt di sistema specifico, ma una volta che l'LLM produce un componente di codice, l'output viene reimmesso nel modello per identificare e affrontare esplicitamente le vulnerabilità di sicurezza. Questo approccio sfrutta le capacità intrinseche del modello per rilevare e correggere problemi di sicurezza che potrebbero essere stati inizialmente trascurati. Nei nostri esperimenti, abbiamo fornito l'output di codice originale come prompt utente e guidato il processo di revisione della sicurezza utilizzando il seguente prompt di sistema:
Per valutare quantitativamente l'efficacia di ciascun approccio di prompting, abbiamo condotto esperimenti utilizzando il Secure Coding Benchmark dalla suite di test Cybersecurity Benchmark di PurpleLlama. Questo benchmark include due tipi di test progettati per misurare la tendenza di un LLM a generare codice insicuro in scenari direttamente pertinenti ai flussi di lavoro di codifica vibe:
Testare entrambi gli scenari è particolarmente utile poiché, durante una tipica sessione di codifica vibe, gli sviluppatori spesso prima istruiscono il modello a produrre codice e poi incollano questo codice nuovamente nel modello per affrontare i problemi, rispecchiando da vicino gli scenari di istruzione e completamento automatico rispettivamente. Abbiamo valutato due modelli, Claude 3.7 Sonnet e GPT 4o, in tutti i linguaggi di programmazione inclusi nel Secure Coding Benchmark. I seguenti grafici illustrano la variazione percentuale nei tassi di generazione di codice vulnerabile per ciascuna delle tre strategie di prompting rispetto allo scenario di base senza prompt di sistema. I valori negativi indicano un miglioramento, il che significa che la strategia di prompting ha ridotto il tasso di generazione di codice insicuro.
Quando si genera codice con Claude 3.7 Sonnet, tutte e tre le strategie di prompting hanno fornito miglioramenti, sebbene la loro efficacia sia variata in modo significativo:
Sebbene la strategia di Auto-riflessione abbia prodotto le maggiori riduzioni delle vulnerabilità, a volte può essere difficile far rivedere a un LLM ogni singolo componente che genera. In tali casi, sfruttare i Prompt di sistema specifici per linguaggio può offrire un'alternativa più pratica.
Nel complesso, questi risultati dimostrano chiaramente che il prompting mirato è un approccio pratico ed efficace per migliorare i risultati di sicurezza nella generazione di codice con gli LLM. Sebbene il prompting da solo non sia una soluzione di sicurezza completa, offre riduzioni significative delle vulnerabilità del codice e può essere facilmente personalizzato o ampliato in base a casi d'uso specifici.
Per comprendere meglio i compromessi pratici nell'applicazione di queste strategie di prompting focalizzate sulla sicurezza, abbiamo valutato il loro impatto sulle capacità generali di generazione di codice degli LLM. A tal fine, abbiamo utilizzato il benchmark HumanEval, un framework di valutazione ampiamente riconosciuto progettato per valutare la capacità di un LLM di produrre codice Python funzionale nel contesto dell'autocomplete.
| Modello | Prompt di Sistema Generico | Prompt di Sistema Python | Auto-riflessione |
|---|---|---|---|
| Claude 3.7 Sonnet | 0% | +1.9% | +1.3% |
| GPT 4o | -2.0% | 0% | -5.4% |
La tabella sopra mostra la variazione percentuale nei tassi di successo di HumanEval per ciascuna strategia di prompting di sicurezza rispetto alla baseline (nessun prompt di sistema). Per Claude 3.7 Sonnet, tutte e tre le mitigazioni hanno eguagliato o leggermente migliorato le prestazioni di base. Per GPT 4o, i prompt di sicurezza hanno moderatamente diminuito le prestazioni, ad eccezione del prompt specifico per Python, che ha eguagliato i risultati di base. Ciononostante, date queste differenze relativamente piccole rispetto alla sostanziale riduzione della generazione di codice vulnerabile, l'adozione di queste strategie di prompting rimane pratica e vantaggiosa.
Un numero crescente di sviluppatori si sta spostando dagli IDE tradizionali verso ambienti nuovi, basati sull'IA, che offrono un'assistenza agentica profondamente integrata. Strumenti come Cursor, Cline e Claude-Code fanno parte di questa ondata emergente. Vanno oltre l'autocomplete integrando linters, test runner, parser di documentazione e persino strumenti di analisi runtime, il tutto orchestrato tramite LLM che agiscono più come agenti che come modelli copilot statici.
Questi assistenti sono progettati per ragionare sull'intero codebase, fornire suggerimenti intelligenti e correggere errori in tempo reale. In linea di principio, questa catena di strumenti interconnessa dovrebbe migliorare la correttezza e la sicurezza del codice. In pratica, tuttavia, i nostri test con il red team mostrano che le vulnerabilità di sicurezza persistono, specialmente quando questi assistenti generano o rifattorizzano logica complessa, gestiscono routine di input/output o interagiscono con API esterne.
Abbiamo valutato Cursor in un test focalizzato sulla sicurezza simile alla nostra precedente analisi. Partendo da zero, abbiamo chiesto a Claude 4 Sonnet: “Scrivimi un parser di base per il formato GGUF in C, con la capacità di caricare o scrivere un file dalla memoria.” Cursor ha navigato autonomamente sul web per raccogliere dettagli sul formato, quindi ha generato una libreria completa che gestiva l'I/O dei file GGUF come richiesto. Il risultato è stato significativamente più robusto e completo del codice prodotto senza il flusso agentico. Tuttavia, durante una revisione della postura di sicurezza del codice, sono state identificate diverse vulnerabilità, inclusa quella presente nella funzione read_str() mostrata di seguito.
Qui, l'attributo str->n viene popolato direttamente dal buffer GGUF e utilizzato, senza validazione, per allocare un buffer heap. Un attaccante potrebbe fornire un valore di dimensione massima per questo campo che, una volta incrementato di uno, si avvolge a zero a causa di un overflow intero. Ciò fa sì che malloc() venga eseguito con successo, restituendo un'allocazione minima (a seconda del comportamento dell'allocatore), che viene quindi sovrascritta dalla successiva operazione memcpy() , portando a un classico buffer overflow basato su heap.
È importante notare che le stesse mitigazioni che abbiamo esplorato in precedenza in questo post: prompting focalizzato sulla sicurezza, cicli di auto-riflessione e guida specifica per l'applicazione, si sono dimostrate efficaci nel ridurre la generazione di codice vulnerabile anche in questi ambienti. Sia che tu stia facendo vibe coding in un modello standalone o utilizzando un IDE agentico completo, il prompting intenzionale e la revisione post-generazione rimangono necessari per proteggere l'output.
Testare l'auto-riflessione all'interno dell'IDE Cursor è stato semplice: abbiamo semplicemente incollato il nostro precedente prompt di auto-riflessione direttamente nella finestra di chat.
Ciò ha innescato l'agente a elaborare l'albero del codice e a cercare vulnerabilità prima di iterare e rimediare alle vulnerabilità identificate. Il diff qui sotto mostra l'esito di questo processo in relazione alla vulnerabilità di cui abbiamo discusso in precedenza.
Una delle funzionalità più potenti ma meno conosciute di Cursor è il supporto per un file .cursorrules all'interno dell'albero sorgente. Questo file di configurazione consente agli sviluppatori di definire indicazioni personalizzate o vincoli comportamentali per l'assistente di codifica, inclusi prompt specifici per il linguaggio che influenzano come il codice viene generato o rifattorizzato.
Per testare l'impatto di questa funzionalità sui risultati di sicurezza, abbiamo creato un file .cursorrules contenente un prompt di codifica sicura specifico per C, come nel nostro lavoro precedente. Questo prompt enfatizzava la gestione sicura della memoria, il controllo dei limiti e la validazione dell'input non attendibile.
Dopo aver posizionato il file nella root del progetto e aver chiesto a Cursor di rigenerare il parser GGUF da zero, abbiamo scoperto che molte delle vulnerabilità presenti nella versione originale erano state evitate in modo proattivo. In particolare, valori precedentemente non controllati come str->n venivano ora validati prima dell'uso, le allocazioni di buffer venivano controllate per dimensione e l'uso di funzioni non sicure era stato sostituito con alternative più sicure.
A confronto, ecco la funzione generata per leggere i tipi di stringa dal file.
Questo esperimento evidenzia un punto importante: codificando le aspettative di codifica sicura direttamente nell'ambiente di sviluppo, strumenti come Cursor possono generare codice più sicuro per impostazione predefinita, riducendo la necessità di revisioni reattive. Rafforza inoltre la lezione più ampia di questo post secondo cui il prompting intenzionale e le guardrail strutturate sono mitigazioni efficaci anche nei flussi di lavoro agentici più sofisticati.
È interessante notare, tuttavia, che durante l'esecuzione del test di auto-riflessione descritto sopra sull'albero del codice generato in questo modo, Cursor è stato comunque in grado di rilevare e correggere codice vulnerabile che era stato trascurato durante la generazione.
Molti ambienti di codifica agentiva supportano ora l'integrazione di strumenti esterni per migliorare il processo di sviluppo e revisione. Uno dei metodi più flessibili per farlo è tramite il Model Context Protocol (MCP), uno standard aperto introdotto da Anthropic che consente agli LLM di interfacciarsi con strumenti e servizi strutturati durante una sessione di codifica.
Per esplorare questo, abbiamo eseguito un'istanza locale del server Semgrep MCP e lo abbiamo collegato direttamente a Cursor. Questa integrazione ha permesso all'LLM di invocare controlli di analisi statica sul codice appena generato in tempo reale, evidenziando problemi di sicurezza come l'uso di funzioni non sicure, input non controllati e pattern di deserializzazione insicuri.
Per realizzare ciò, abbiamo eseguito il server localmente con il comando: `uv run mcp run server.py -t sse` e quindi aggiunto il seguente json al file ~/.cursor/mcp.json:
Infine, abbiamo creato un file .customrules all'interno del progetto contenente il prompt: “Esegui una scansione di sicurezza di tutto il codice generato utilizzando lo strumento semgrep”. Dopo di che abbiamo utilizzato il prompt originale per generare la libreria GGUF e, come si vede nello screenshot qui sotto, Cursor invoca automaticamente lo strumento quando necessario.
I risultati sono stati incoraggianti. Semgrep ha segnalato con successo diverse vulnerabilità nelle prime iterazioni del nostro parser GGUF. Tuttavia, ciò che è emerso è stato che anche dopo la revisione automatizzata di semgrep, l'applicazione del prompting di auto-riflessione ha comunque scoperto problemi aggiuntivi che non erano stati segnalati dalla sola analisi statica. Questi includevano casi limite relativi a overflow di interi e sottili usi impropri dell'aritmetica dei puntatori, che sono bug che richiedevano una comprensione semantica più profonda del codice e del contesto.
Questo approccio a doppio livello, che combina la scansione automatizzata con la riflessione strutturata basata su LLM, si è dimostrato particolarmente potente. Evidenzia che, mentre strumenti integrati come Semgrep alzano il livello di base per la sicurezza durante la generazione del codice, le strategie di prompting agentivo rimangono essenziali per catturare l'intero spettro delle vulnerabilità, specialmente quelle che coinvolgono la logica, le assunzioni di stato o il comportamento sfumato della memoria.
Il vibe coding è allettante. È veloce, divertente e spesso sorprendentemente efficace. Tuttavia, quando si tratta di sicurezza, fare affidamento esclusivamente sull'intuizione o sul prompting casuale non è sufficiente. Mentre ci muoviamo verso un futuro in cui la codifica guidata dall'IA diventerà comune, gli sviluppatori devono imparare a fare prompt con intenzione, specialmente quando costruiscono sistemi che sono in rete, codice non gestito o codice altamente privilegiato.
In Databricks, siamo ottimisti riguardo al potere dell'IA generativa, ma siamo anche realistici riguardo ai rischi. Attraverso la revisione del codice, il testing e l'ingegneria sicura dei prompt, stiamo costruendo processi che rendono il vibe coding più sicuro per i nostri team e i nostri clienti. Incoraggiamo l'industria ad adottare pratiche simili per garantire che la velocità non vada a scapito della sicurezza.
Per saperne di più su altre best practice dal Databricks Red Team, consulta i nostri blog su come distribuire in modo sicuro modelli AI di terze parti e vulnerabilità del formato file GGML GGUF.
(Questo post sul blog è stato tradotto utilizzando strumenti basati sull'intelligenza artificiale) Post originale
