Sicurezza di siti e app IA

Script greppabile: cercare le 7 falle in un repo in un comando

12 min di lettura
Monocolo con montatura dorata e cordino nero appoggiato sulla pagina di un vecchio libro stampato in caratteri gotici, illuminata da luce radente
Windows macOS Linux
Foto: Pixabay · Pexels · Licenza Pexels

Un file di pattern e un solo comando (PowerShell, ripgrep o grep) per trovare in tutto il progetto le 7 falle tipiche del codice scritto dall’IA.

La guida alle 7 falle di sicurezza nel codice scritto dall’IA ti dà, falla per falla, le stringhe da cercare con Ctrl+F. Funziona su un file; su un progetto con decine di file diventa un lavoro lungo e facile da sbagliare. Qui trasformi quella lista in un file di pattern e lo passi a un solo comando che legge tutta la cartella. Il comando non dice se il codice è sicuro: ti porta dritto alle righe da leggere con attenzione.

La risposta breve: salvi i pattern in falle.txt nella cartella del progetto, poi lanci Select-String su Windows (PowerShell), rg (ripgrep) su qualsiasi sistema oppure grep su macOS e Linux. Sul progetto di prova, con file vulnerabili scritti apposta, i tre comandi hanno restituito le stesse 8 righe. Due casi restano fuori: except e pass scritti su due righe (serve un secondo comando) e i pacchetti inventati, che si verificano su PyPI e non nel codice.

ℹ️ Nota / Prerequisito operativo: ti serve un terminale aperto nella cartella principale del progetto. Su Windows usa PowerShell (il Prompt dei comandi ha findstr, che non gestisce questi pattern); su macOS e Linux il Terminale. grep è già presente su macOS e Linux; ripgrep va installato a parte ed è facoltativo.

Il file dei pattern

Crea nella cartella del progetto un file di testo chiamato falle.txt con queste righe, una per pattern. Ogni riga corrisponde a una falla della guida madre.

(password|passwd|api_?key|secret|token)[A-Za-z0-9_]*\s*=\s*["'][^"']{4,}
shell\s*=\s*True
os\.system\(
verify\s*=\s*False
check_hostname\s*=\s*False
except[^:]*:\s*pass
0\.0\.0\.0
(^|[^A-Za-z0-9_.])(eval|exec)\(
pickle\.loads?\(

Cosa cerca ogni riga:

  • Riga 1, credenziali in chiaro (falla 1): un nome come password, api_key, secret o token (anche API_KEY_PROD), poi = e un testo tra virgolette di almeno 4 caratteri. Non segnala DB_PASSWORD = os.environ.get(...), che è il modo corretto.
  • Righe 2 e 3, comandi di sistema (falla 2): shell=True, anche scritto con gli spazi, e os.system(.
  • Righe 4 e 5, rete senza verifica (falla 3): verify=False e check_hostname=False.
  • Riga 6, errori silenziati (falla 4): except seguito da pass sulla stessa riga.
  • Riga 7, app aperta a tutta la rete (falla 5): 0.0.0.0.
  • Righe 8 e 9, codice eseguito dai dati (falla 6): eval( ed exec( non preceduti da lettere o da un punto, così ast.literal_eval( non compare; poi pickle.load( e pickle.loads(.

I pattern usano solo costrutti che PowerShell, ripgrep, GNU grep e il grep di macOS interpretano allo stesso modo. Per questo la riga 1 scrive [A-Za-z0-9_] invece della scorciatoia \w.

💡 Consiglio pratico — Il file vive nel progetto: tieni falle.txt nella cartella principale e aggiungilo al controllo di versione. Quando scopri un nuovo schema pericoloso, aggiungi una riga e il controllo successivo lo cerca già.

Il comando, sistema per sistema

Windows (PowerShell):

Get-ChildItem -Recurse -File -Include *.py |
  Where-Object { $_.FullName -notmatch '[\\/](\.venv|venv|node_modules|\.git)[\\/]' } |
  Select-String -Pattern (Get-Content .\falle.txt)
  • Get-ChildItem -Recurse -File -Include *.py: elenca tutti i file .py della cartella e delle sottocartelle.
  • Where-Object { ... -notmatch ... }: scarta i file dentro .venv, venv, node_modules e .git, cioè librerie di terzi e storico di Git.
  • Select-String -Pattern (Get-Content .\falle.txt): legge i pattern dal file e cerca ognuno in ogni file. Select-String ignora già maiuscole e minuscole.

Qualsiasi sistema, con ripgrep:

rg -n -i -f falle.txt -g '*.py' .
  • -n: mostra il numero di riga.
  • -i: ignora maiuscole e minuscole, così trova anche API_KEY e Shell=True.
  • -f falle.txt: legge i pattern dal file, uno per riga.
  • -g '*.py': limita la ricerca ai file Python.
  • .: parte dalla cartella corrente.

ripgrep salta da solo le cartelle nascoste come .venv e .git e quelle elencate in .gitignore. Su Windows lo stesso comando funziona in PowerShell. Per installarlo: winget install BurntSushi.ripgrep.MSVC su Windows, brew install ripgrep su macOS, sudo apt install ripgrep su Debian e Ubuntu (comandi dalla documentazione ufficiale di ripgrep).

macOS / Linux, con grep:

grep -rniE -f falle.txt --include='*.py' --exclude-dir={.venv,venv,node_modules,.git} .
  • -r: cerca in tutte le sottocartelle.
  • -n e -i: numero di riga e nessuna differenza tra maiuscole e minuscole.
  • -E: usa le espressioni regolari estese, necessarie per |, ? e {4,}.
  • -f falle.txt: legge i pattern dal file.
  • --include='*.py': solo file Python.
  • --exclude-dir={...}: salta le cartelle elencate. Le parentesi graffe le espande la shell (bash o zsh) in quattro opzioni separate.

Le combinazioni utili: aggiungi -c (sia a grep sia a rg) per avere solo il conteggio per file, oppure togli --include e -g per controllare anche file .js o .ts (i pattern 1, 7 e 8 valgono anche lì).

Il risultato sul progetto di prova

Il progetto di prova ha due file in app/ con le falle scritte apposta, una copia di eval dentro .venv/ (da non segnalare), un test che usa ast.literal_eval (da non segnalare) e una password letta correttamente dalle variabili d’ambiente. Output di grep su Debian 13 (GNU grep 3.11):

./app/utils.py:3:token = 'abc123-finto'
./app/utils.py:7:    os.system("rm -rf " + cartella)
./app/server.py:9:API_KEY = "sk-ESEMPIO-NON-REALE-123"
./app/server.py:14:    subprocess.run(f"ping -c 1 {host}", shell=True)
./app/server.py:18:    return requests.get(url, verify=False)
./app/server.py:23:        return pickle.load(f)
./app/server.py:34:    return eval(espressione)
./app/server.py:38:    app.run(host="0.0.0.0", port=5000, debug=True)

ripgrep 14.1.1 ha dato le stesse 8 righe nello stesso formato; PowerShell 7.5.4 le stesse 8, con i percorsi senza ./ iniziale e i file in ordine alfabetico. Nessuna riga da .venv/, nessun literal_eval, nessun DB_PASSWORD. Su macOS 15.7.7 il grep di sistema (BSD grep 2.6.0) ha riconosciuto gli stessi pattern sulle stesse righe di prova. La chiave dell’esempio è inventata.

Quando non trova nulla, grep e rg restituiscono il codice di uscita 1 e non stampano niente. Nessun output significa solo che nessun pattern compare, non che il codice è sicuro.

Il secondo comando: except e pass su due righe

La riga 6 di falle.txt trova except Exception: pass scritto su una riga sola. Nel codice generato dall’IA lo trovi spesso così:

    except Exception:
        pass

Un comando che legge riga per riga non vede le due righe insieme. La soluzione: cercare gli except che finiscono con i due punti e mostrare anche la riga dopo.

Windows (PowerShell):

Get-ChildItem -Recurse -File -Include *.py |
  Where-Object { $_.FullName -notmatch '[\\/](\.venv|venv|node_modules|\.git)[\\/]' } |
  Select-String -Pattern 'except[^:]*:\s*$' -Context 0,1

macOS / Linux:

grep -rnE -A1 'except[^:]*:\s*$' --include='*.py' --exclude-dir={.venv,venv,node_modules,.git} .

Con ripgrep puoi cercare direttamente le due righe con -U (ricerca su più righe):

rg -n -U -i 'except[^:\n]*:\s*\n\s*pass\b' -g '*.py' .

-Context 0,1 e -A1 aggiungono la riga successiva a ogni risultato: tocca a te guardare se è pass. Sul progetto di prova tutti e tre hanno trovato i due casi, app/server.py righe 29-30 e app/utils.py righe 13-14. Output di grep:

./app/utils.py:13:    except:
./app/utils.py-14-        pass
--
./app/server.py:29:    except Exception:
./app/server.py-30-        pass

La falla 7 non si cerca nel codice

I pacchetti inventati dall’IA non lasciano tracce nel codice Python: stanno nei comandi di installazione. Puoi elencare tutte le righe pip install di README, file di testo e script, poi controllare a mano ogni nome su pypi.org:

rg -n -i 'pip3? install' -g '*.md' -g '*.txt' -g '*.sh' .

Sul progetto di prova:

./README.md:3:pip install flask requests super-fast-pdf-cleaner

Per ogni nome guarda anche requirements.txt. Se un pacchetto non esiste su PyPI, o esiste da pochi giorni con un solo autore e nessun progetto collegato, non installarlo. I controlli passo per passo, anche per npm, sono nella guida su come verificare un pacchetto inventato dall’IA. Con grep su macOS e Linux: grep -rniE 'pip3? install' --include='*.md' --include='*.txt' --include='*.sh' .

Cosa il comando non trova

Il comando trova testo, non capisce il codice. Questo ha due conseguenze.

Mani che setacciano farina con un colino di metallo sopra una teglia scura, con uova, pomodorini e spaghetti in primo piano
Come un setaccio: il comando trattiene quello che somiglia a una falla, ma cosa tenere lo decidi tu guardando. Foto: Klaus Nielsen · Pexels

I falsi negativi: nella prova non sono stati segnalati una chiave costruita in due pezzi (chiave = "sk-" + resto), un dizionario {"shell": True} passato come opzioni, e from pickle import load seguito da load(f). Un risultato vuoto non è un certificato.

I falsi positivi: la stessa riga di testo può comparire in un commento, in un messaggio a schermo o in un test. Lanciando il comando grep sugli script scaricabili di questo sito, per esempio, è comparso anche print(...) di uno scanner che nomina eval per spiegarlo: era testo, non una chiamata. Ogni risultato va aperto e letto nel suo contesto.

⚠️ Attenzione — Non cancellare righe a colpo d’occhio: il comando ti dice dove guardare, non cosa cambiare. Per correggere shell=True ed eval senza rompere il programma, segui le guide dedicate prima di toccare il codice.

Per un controllo che legge la struttura del codice servono strumenti di analisi statica come Bandit; questo comando resta il primo passaggio, rapido e senza installazioni. Se preferisci lavorare dall’editor, le stesse verifiche come ricerche da incollare in VS Code o Cursor sono in Find in Files: stringhe spia per revisionare codice generato.

Errori tipici

  • Su Windows il comando non trova nulla anche se le falle ci sono → lo hai lanciato nel Prompt dei comandi o hai usato findstr → apri PowerShell nella cartella del progetto e usa il blocco Get-ChildItem ... | Select-String.
  • Errore che dice che falle.txt non esiste (con grep: grep: falle.txt: No such file or directory) → il terminale non è nella cartella dove hai salvato falle.txt → spostati nella cartella principale del progetto con cd prima di lanciare il comando.
  • Centinaia di risultati da librerie che non hai scritto tu → l’ambiente virtuale ha un nome diverso da .venv o venv → aggiungi quel nome all’elenco di --exclude-dir (grep) o alla parte tra parentesi di -notmatch (PowerShell); ripgrep salta già le cartelle elencate in .gitignore.
  • grep: falle.txt:3: Unmatched ( or \( e nessun risultato → manca -E, quindi grep legge i pattern con le regole di base e non capisce parentesi e | → ricopia il comando con -rniE.
  • rg: command not found o rg non è riconosciuto → ripgrep non è installato o il terminale è stato aperto prima dell’installazione → installalo con winget, brew o apt e apri un terminale nuovo.

Domande Frequenti (FAQ)

Funziona anche su progetti JavaScript o TypeScript?

In parte. Le righe per le credenziali, per 0.0.0.0 e per eval( valgono anche in JavaScript: basta cambiare *.py in *.js o *.ts. Le altre righe sono specifiche di Python; per JavaScript andrebbero aggiunti schemi come child_process.exec( o rejectUnauthorized: false.

Perché non usare findstr sul Prompt dei comandi?

findstr supporta solo un sottoinsieme ridotto delle espressioni regolari: non conosce | per le alternative né le ripetizioni come {4,}. I pattern di falle.txt andrebbero spezzati e semplificati, con più falsi positivi. PowerShell è già presente su Windows e legge lo stesso file senza modifiche.

Posso salvare il comando in uno script da lanciare con un doppio clic?

Sì: salva il blocco PowerShell in un file .ps1 o quello grep in un file .sh accanto a falle.txt. Su Windows i criteri di esecuzione di PowerShell possono bloccare gli script locali; in quel caso incolla il comando nel terminale.

Il comando trova anche chiavi API vere dentro i file .env?

Solo se aggiungi .env ai file da cercare: con ripgrep basta rg -n -i -f falle.txt -g '.env' ., che nella prova ha letto .env anche se era nascosto ed elencato in .gitignore. Le chiavi in .env sono al posto giusto; il problema nasce se .env finisce nel controllo di versione. Per quello controlla che .env sia elencato in .gitignore.

Quanto spesso conviene lanciarlo?

Ogni volta che l’IA ha generato o riscritto parti del progetto, e prima di consegnarlo o metterlo online.

Dopo il controllo

Apri ogni riga trovata, decidi se è un problema reale e correggila con la guida della falla corrispondente: per i comandi di sistema i pattern sicuri per shell=True e subprocess in Python, per eval e pickle le alternative sicure per dati e configurazione. Poi rilancia il comando: l’obiettivo è un elenco di sole righe che hai letto e deciso di tenere. Se preferisci che sia qualcun altro a passare il progetto riga per riga, è un lavoro che faccio anche su richiesta. Per il quadro completo e la tabella dell’audit in 10 minuti, torna alla guida madre sulle 7 falle di sicurezza nel codice scritto dall’IA.

Alex Lunalbi

Alex Lunalbi

Security & privacy

Dona su PayPal

Scrivo di sicurezza digitale pratica e codice IA. Su ai riparo ogni procedura è verificata su macchine reali.

Tag
  • sicurezza,
  • grep,
  • ripgrep,
  • powershell,
  • vibe coding