L’AI può davvero scrivere codice sicuro al 100%? Ecco cosa ho scoperto testandola
Ho chiesto all’AI codice «sicuro al 100%» e l’ho testato in locale. La sicurezza non è un toggle: metodo, pattern ricorrenti e come revisionare.
Ricevo spesso mail con una variazione della stessa domanda: «Se chiedo esplicitamente a ChatGPT o Claude di scrivermi un codice sicuro al 100%, posso metterlo online e stare tranquillo?».
Chi fa vibe coding — costruisce pezzi di progetto descrivendoli a un modello, senza padroneggiare ogni riga — spera che una parola magica nel prompt funzioni da interruttore: spegne i bug, accende le difese.
Non ho risposto solo con teoria. Ho isolato una cartella di test in locale, ho scelto compiti tipici (login, upload, chiamata API, form) e li ho sottoposti a modelli di uso comune chiedendo esplicitamente il massimo di sicurezza. Poi ho revisionato l’output come farei su un pezzo destinato al pubblico.
Spoiler onesto: l’interruttore non esiste. Quello che emerge non è un elenco di disastri inevitabili, ma un fatto stabile: l’IA ha un modello mentale della sicurezza diverso dal tuo. Qui spiego perché il «100% sicuro» è un’illusione linguistica, come ho strutturato i test, quali pattern tornano spesso e quale protocollo di revisione puoi usare prima di pubblicare.
ℹ️ Nota / Prerequisito operativo
Ambiente di prova: cartella isolata su Windows (C:\Users\TuoNome\prove-ai-sicurezza\). Su macOS/Linux usa un path analogo sotto la home (~/prove-ai-sicurezza/). Non eseguire codice generato con accesso rete/file system senza averlo letto. I modelli cambiano spesso: i pattern contano più di una classifica “X batte Y”.
Cosa significherebbe «sicuro al 100%»?
Prima di giudicare un LLM, serve un chiarimento: in informatica la sicurezza al 100% è una categoria vuota. Non esiste codice perfetto in assoluto; esiste codice adeguato al modello di minaccia (threat model) per cui è stato scritto.
Quando chiedi «uno script sicuro per il login», il modello non conosce il tuo contesto: Raspberry Pi in LAN di casa (rischio basso, attaccante occasionale) oppure VPS esposto (bot che provano milioni di password). Senza quel perimetro non può “blindare ogni vettore”.
⚠️ Attenzione — Il mito dell’assoluto
Diffida di qualsiasi strumento — umano o artificiale — che garantisca sicurezza assoluta. È riduzione continua del rischio, non uno stato definitivo.
| Cosa chiedi nel prompt | L’IA può “misurarlo”? | Perché dipende da te e dall’ambiente |
|---|---|---|
| «Proteggi i dati degli utenti» Hashing, storage, cifratura | Dipende | Può scegliere librerie sensate (es. bcrypt / argon2), ma non sa se configurerai TLS, backup e permessi DB correttamente. |
| «Niente XSS / SQLi» Input e query | In parte | Può usare query parametrizzate e escape; una tua modifica successiva o una dipendenza vecchia possono riaprire la falla. |
| «Usa dipendenze sicure» Supply chain | No | I modelli sono ancorati a corpora passati; possono citare pacchetti obsoleti, vulnerabili o addirittura allucinati. |
Chiedere codice «sicuro» senza definire rispetto a cosa è come chiedere una casa «a prova di tutto» senza dire se è in zona sismica o in palude.
Come l’ho testata: il mio protocollo
Per evitare opinioni astratte ho usato un protocollo ripetibile. Non ho cercato di far generare malware (lì entrano i filtri di policy); ho replicato scenari di sviluppo comuni.
Setup (Windows first)
- Crea una cartella isolata, es.
C:\Users\TuoNome\prove-ai-sicurezza\. - Su macOS/Linux:
mkdir -p ~/prove-ai-sicurezza && cd ~/prove-ai-sicurezza. - Lavoraci dentro (venv Python dedicato se serve). Non mischiare con progetti di produzione.
- Salva ogni risposta del modello in un file distinto (
login_v1.py,upload_v1.py, …) così puoi confrontare le revisioni.
Quattro scenari
- Login base in Python (Flask o FastAPI).
- Upload file da utente.
- Script che chiama un’API esterna con
requests. - Form HTML + backend di ricezione dati.
Prompt forzato (stesso schema ogni volta)
Esempio reale di istruzione ripetuta:
Scrivi un server Flask per l’upload di file. Deve essere pronto per la produzione, robusto e sicuro al 100% contro ogni tipo di attacco. Aggiungi tutte le difese necessarie.
Griglia di valutazione
Per ogni output ho chiesto:
- Parte senza errori di sintassi evidenti?
- Ci sono segreti / API key / password hardcoded?
- Gli input utente sono validati prima di toccare DB o disco?
- Nelle chiamate di rete i certificati TLS restano verificati (
verify=Truedi default)? - Il tono della risposta: avverte dei limiti o consegna il codice con totale sicurezza retorica?
Sessioni vergini (niente contesto lungo che “ricorda” patch precedenti). Non pubblico una classifica modello‑per‑modello: gli aggiornamenti spostano i numeri; i pattern restano.
Cosa non ho misurato (onestà di metodo)
Non ho valutato:
- red team completo né fuzzing automatico;
- conformità legale (GDPR “by design” non si risolve con un prompt);
- modelli specializzati enterprise dietro policy private.
Ho misurato ciò che fa chi pubblica un MVP dopo due chat: funziona in locale? e il tono del modello è rassicurante? Su quel piano il «100% sicuro» fallisce in modo ripetibile.
Cosa succede quando chiedi «sicurezza assoluta»
Risultato più stabile: aggiungere «sicuro al 100%» migliora l’impalcatura rispetto a un prompt neutro, ma fallisce spesso su dettagli di configurazione e trade‑off «far funzionare vs difendere».
1. Sicurezza performativa
Commenti professionali, hashing con librerie note, debug=False su Flask… best practice di superficie. Il modello ottimizza per codice che sembra sicuro.
2. Trust implicito e verify=False
Nello scenario API, la gestione degli status HTTP era spesso decente. Di fronte a un errore locale legato ai certificati, però, è emerso il classico suggerimento: verify=False su requests.get().
Quel flag spegne la validazione TLS lato client e apre la porta a MitM. Il modello ha privilegiato «lo script parte» rispetto al vincolo di sicurezza iniziale. Se lo trovi in un progetto destinato a Internet, trattalo come allarme rosso.
3. Upload: blocklist invece di allowlist
Estensioni .exe / .sh bloccate: ok in apparenza. Ma le blocklist si aggirano; un’allowlist (jpg/png/webp e basta) è più onesta. In più, salvare nella stessa tree dell’app senza isolamento rende più facile scenari di upload pericolosi se la validazione è debole.
4. Binding troppo generoso
Per “farti testare da un altro device”, i tutorial e i modelli tendono a host="0.0.0.0". In LAN di casa può essere voluto; su VPS senza firewall è un invito. Il prompt «100% sicuro» non corregge automaticamente questa scelta di comodità.
| Area | Esito tipico | Rischio reale |
|---|---|---|
| Login / hashing Password a riposo | Spesso ok | Algoritmi sensati; manca di frequente il rate limiting / lockout. |
| Database SQL injection | Incostante | Con ORM va meglio; con SQL “a mano” possono ricomparire concatenazioni. |
Rete / binding
0.0.0.0, TLS client
|
Alto | Esposizione ampia “per comodità”; a volte verify=False. |
ℹ️ Nota
Un catalogo tecnico dei difetti tipici sta in 7 falle di sicurezza nel codice generato dall’IA. Questo pezzo è il metodo: perché succede e come revisionare.
Il rischio peggiore non è solo il bug: è il falso positivo di fiducia. Compila, la pagina risponde, nessun traceback rosso → «è pronto». Senza revisione, no.
Perché succede: il modello mentale dell’IA
Un LLM non “ragiona” su firewall e budget di rischio come un human reviewer dedicato. Prevede token plausibili su corpora enormi di codice pubblico.
Su GitHub e forum abbondano snippet per far funzionare subito, workaround e tutorial semplificati. Disabilitare controlli SSL, aprire 0.0.0.0, concatenare SQL “perché è più chiaro” sono pattern statisticamente presenti. Il modello li riproduce quando riducono l’attrito.
Due metriche implicite guidano spesso l’output:
- Plausibilità visiva — sembra codice da sviluppatore competente.
- Successo immediato — meno errori al primo run.
Difendere un’app richiede frizione: rifiutare input, far fallire connessioni, negare permessi. Senza un threat model che tu imponi, il percorso a bassa frizione vince.
Cosa l’IA fa davvero bene
Non è un rischio puro. Funziona se rovesci il ruolo: non architetto di sicurezza autonomo, ma esecutore di regole che hai deciso tu.
Nei test brillava su:
- Boilerplate con vincoli stretti — «hash con argon2, minimo 16 caratteri, niente
eval»: output spesso pulito. - Spiegare patch — header mancante, CVE descritta da uno scanner: ottima come tutore.
- Checklist applicata al diff — «rivedi: ogni query parametrizzata, niente secret in chiaro»: metodica se la lista è concreta.
Il problema non è usare Cursor o la chat. È trattarli come sostituto del threat model e della revisione.
Protocollo di revisione post‑generazione
Prima di esporre qualcosa in Rete, questi cinque passi (Windows, macOS e Linux: stessi concetti; cambiano solo path e scorciatoie IDE).
Passaggio 1 — Autenticazione, I/O, rete
Non serve rileggere CSS e layout. Microscopio su:
- sessioni/cookie (
HttpOnly,Secure,SameSitedove applicabile); - ogni input utente prima di DB/filesystem;
- binding (
127.0.0.1vs0.0.0.0) e HTTPS lato client.
Passaggio 2 — Find in Files sulle stringhe spia
Nell’editor (VS Code, Cursor, …) cerca nel progetto:
verify=False,ssl=Falseshell=True(subprocess Python)0.0.0.0- concatenazioni tipo
SELECT ...+ f‑string /+su input utente eval(,exec(,pickle.loads(su dati non fidati
Su Windows, se preferisci la shell invece dell’IDE:
cd C:\Users\TuoNome\prove-ai-sicurezza
Select-String -Path *.* -Pattern 'verify=False','shell=True','0.0.0.0' -SimpleMatch -Recurse
Su macOS / Linux:
cd ~/prove-ai-sicurezza
rg -n "verify=False|shell=True|0\.0\.0\.0" .
(Se non hai rg, usa grep -R.) L’obiettivo non è una scansione da auditor: è catturare i default pericolosi che i modelli inseriscono per far partire lo script.
Passaggio 3 — Nessun segreto nel repo
Placeholder tipo api_key = "LA_TUA_CHIAVE" sono didattici finché non ci incollo la chiave vera e push su GitHub. Variabili d’ambiente + .env nel .gitignore. Per un giro strutturato sulle dipendenze: npm audit / supply chain.
Passaggio 4 — Dipendenze reali (no hallucination)
Se il modello inventa un pacchetto “perfetto”, verifica su PyPI/npm ufficiali. I nomi allucinati sono un vettore noto di typosquatting.
Passaggio 5 — Mai produzione al primo run
«Gira sul mio PC» ≠ pronto per Internet. Ambiente chiuso, backup della cartella prima di script che toccano file di sistema, e — se pubblichi un sito — un audit gratuito di base prima di chiudere.
🚨 Avviso critico — Esecuzione alla cieca
Codice che tocca disco o rete: leggilo. Se una funzione non ti è chiara, chiedi: «Spiega riga per riga cosa fa e quali permessi richiede». Non eseguire e “vedere che succede”.
Trend: cosa cambia per chi pubblica siti
Il vibe coding accelera l’idea → app. Se la produzione di codice cresce e la revisione umana cala, aumenta la superficie di attacco fatta di errori banali (SQLi classiche, key in repo, CORS aperti) non di exploit da film.
In pratica vedo tre profili: (1) chi genera e pubblica lo stesso giorno; (2) chi genera, fa smoke test sul form e chiude; (3) chi genera, applica una checklist e solo dopo espone la porta. Solo il terzo riduce in modo credibile il debito di sicurezza. Gli altri due spiegano perché «Task completed» nei log dell’assistente non è un criterio di go‑live.
Per il contesto rischi generali del flusso AI‑assisted: vibe coding e rischi di sicurezza. La revisione strutturata non è un lusso enterprise: è il prezzo del bottone «genera».
Errori comuni
- Credere al disclaimer rassicurante del modello — «include solide misure…» è testo generato. Valuta il codice, non il tono.
- Un solo prompt “rendi invulnerabile” — vago → performativo. Spezza: feature prima, poi sessione dedicata a CORS / validazione / auth.
- Testare solo il frontend — form ok ≠ server a prova di input ostile. Prova file sbagliati, caratteri strani, richieste manipolate.
- Sostituire l’intero file dalla chat — rigenerando tutto, il modello “dimentica” vincoli precedenti. Preferisci patch mirate o diff riga per riga.
- Saltare l’OS — path Windows vs POSIX, permessi file, come avvii il server: dettagli banali che l’IA inventa con security theater.
Domande Frequenti (FAQ)
L’AI può scrivere codice sicuro al 100%?
No strumento e nessun umano può certificare il 100%. Con requisiti precisi e un threat model chiaro puoi ottenere codice robusto; con richieste generiche ottieni spesso sicurezza performativa.
Basta chiedere «scrivi codice sicuro» nel prompt?
No. Meglio vincoli misurabili: query parametrizzate, niente eval, allowlist upload, niente verify=False, binding esplicito. La libreria copia-incolla li raccoglie modulo per modulo.
ChatGPT, Claude, Cursor: sono diversi sulla sicurezza?
Sì nei dettagli e nei filtri, no nella natura statistica. Nessuno elimina la revisione umana.
Come revisiono se non sono esperto?
Parti dalla checklist qui: secret in chiaro, dove si salvano i file, stringhe spia, spiegazione dei permessi. Usa i pezzi del cluster (7 falle, audit, npm audit) come mappe, non come sostituto del leggere il tuo codice.
Qual è la differenza con l’articolo sulle 7 falle?
Questo pezzo è metodo ed epistemologia del test («perché il 100% fallisce»). Le 7 falle sono il catalogo tecnico del cosa trovare.
Posso pubblicare se “in locale funziona” e il modello dice che è sicuro?
No. Funziona ≠ difeso. Almeno: Find in Files, niente secret nel repo, prova input ostili, audit di base se è un sito esposto.
In sintesi
Fidati della spiegazione dell’IA quanto ti serve per imparare; verifica la logica; non fare sconti su dipendenze, rete e autenticazione. Il vibe coding resta potentissimo se aggiungi un gradino obbligatorio: revisione con checklist, non fiducia nel toggle «100% sicuro».