Package hallucination: come verificare un pacchetto inventato dall’IA
L’IA ti propone un pip install o un npm install con un nome mai visto? Tre controlli da terminale per capire se il pacchetto esiste ed è quello giusto.
Nel protocollo di revisione del codice generato dall’IA il quarto passaggio dice di verificare che le dipendenze siano reali. Qui lo trasformi in tre controlli concreti, da fare prima di qualsiasi pip install o npm install che arriva da una chat. Un nome di pacchetto proposto dall’IA è un’ipotesi: diventa una dipendenza solo dopo che hai controllato che esiste, che è quello giusto e chi lo pubblica.
La risposta breve: chiedi al registro se il nome esiste (pip index versions per Python, npm view per JavaScript), confrontalo con la documentazione ufficiale della libreria, poi guarda da quanto tempo esiste, quante versioni ha e chi lo mantiene. Nella prova un nome inventato ha prodotto un errore in meno di un secondo, su PyPI e su npm. Se il nome esiste da pochi giorni con una sola versione, ti fermi e controlli a mano.
ℹ️ Nota / Prerequisito operativo: ti serve un terminale e Python installato. Su Windows usa
py(il launcher installato con Python); su macOS e Linuxpython3. Per i controlli su npm serve Node.js. Nessuno dei comandi di questa pagina installa qualcosa: leggono solo le informazioni pubbliche dei registri.
Perché l’IA inventa nomi di pacchetti
Un modello linguistico non consulta PyPI o npm mentre scrive: produce il nome che gli sembra più probabile, che a volte non esiste. Il fenomeno si chiama package hallucination ed è stato misurato: lo studio We Have a Package for You!, presentato a USENIX Security 2025, ha analizzato 576.000 esempi di codice Python e JavaScript generati da 16 modelli. La quota media di pacchetti inesistenti è risultata di almeno il 5,2% per i modelli commerciali e del 21,7% per quelli open source.
Il nome inventato diventa un rischio quando qualcuno lo registra davvero. In una ricerca pubblicata nel 2024, Bar Lanyado ha notato che i modelli suggerivano pip install huggingface-cli, un pacchetto che non esisteva (quello corretto è huggingface_hub). Lo ha registrato vuoto per prova: in tre mesi ha ricevuto più di 30.000 download, come racconta nella ricerca di Lasso Security. Al suo posto poteva esserci codice malevolo. Questa pratica oggi ha un nome, slopsquatting: registrare in anticipo i nomi inventati dai modelli.
Controllo 1: il nome esiste?
Prima di installare, chiedi al registro se il nome c’è. Il comando pip index versions elenca le versioni disponibili su PyPI senza scaricare nulla.
Windows:
py -m pip index versions super-fast-pdf-cleaner
macOS / Linux:
python3 -m pip index versions super-fast-pdf-cleaner
super-fast-pdf-cleaner è il nome inventato che uso negli esempi di questo sito. Output su Debian 13 con pip 26.2.2:
ERROR: No matching distribution found for super-fast-pdf-cleaner
Con un pacchetto reale la risposta cambia. Per requests la prima riga è requests (2.34.2), seguita dall’elenco di tutte le versioni pubblicate.
Per JavaScript il comando equivalente è npm view, uguale su tutti i sistemi:
npm view super-fast-pdf-cleaner
npm error code E404
npm error 404 Not Found - GET https://registry.npmjs.org/super-fast-pdf-cleaner - Not found
Un ERROR o un E404 qui è una buona notizia: il nome non esiste e non c’è nulla da installare. Il caso pericoloso è il contrario, un nome inventato che qualcuno ha registrato. Per questo servono gli altri due controlli.
⚠️ Attenzione —
No matching distributionnon significa sempre «non esiste»: pip dà lo stesso errore anche quando il pacchetto esiste ma non ha versioni compatibili con il tuo Python. Prima di concludere, aprihttps://pypi.org/project/nome-pacchetto/nel browser: se la pagina risponde con un errore 404, il nome non è registrato.
Controllo 2: esiste, ma è quello giusto?
Una fonte di nomi sbagliati facile da riconoscere è la differenza tra il nome che usi nel codice e il nome con cui il pacchetto si installa. Il codice scrive import yaml, ma su PyPI la libreria si chiama PyYAML; scrive import cv2, ma il pacchetto è opencv-python. Se l’IA copia il nome dell’import nel comando di installazione, ottieni pip install yaml. Il giorno della prova yaml e cv2 non esistevano su PyPI (la JSON API rispondeva 404), quindi quel comando sarebbe fallito. Se qualcuno registrasse quei nomi, lo stesso comando installerebbe il suo pacchetto.
La regola pratica: il nome da installare si copia dalla documentazione ufficiale della libreria, non dalla chat. Se non trovi la libreria da nessuna parte fuori dalla chat, trattala come inventata.
Due segnali di nome sospetto, anche quando il pacchetto esiste:
- Il nome è quasi uguale a una libreria famosa: una lettera in meno, un trattino al posto del trattino basso, un
-pythono un-cliaggiunto. È lo schema del typosquatting, descritto nella guida su quando usare pip install e quando no. - Il nome descrive troppo bene il tuo problema, come
flask-secure-upload-guard: un nome che «suona giusto» per il compito che hai chiesto non prova nulla.
Controllo 3: età, versioni e manutentori
Quando il nome esiste, servono tre informazioni: da quando è pubblicato, quante versioni ha e chi lo mantiene. PyPI e npm le espongono in formato JSON (la JSON API di PyPI e il registro di npm). Questo script usa solo la libreria standard di Python e le legge per te. Salvalo come verifica_pacchetto.py in una cartella qualsiasi:
"""Controlla un pacchetto su PyPI o npm prima di installarlo."""
import json
import sys
import urllib.error
import urllib.request
from datetime import date
def scarica_json(url):
try:
with urllib.request.urlopen(url, timeout=15) as risposta:
return json.load(risposta)
except urllib.error.HTTPError as errore:
if errore.code == 404:
return None
raise
def stampa(nome, versione, prima, n_versioni, manutentori, link):
giorni = (date.today() - date.fromisoformat(prima)).days
print(f"Nome: {nome} ultima versione: {versione} versioni: {n_versioni}")
print(f"Prima pubblicazione: {prima} ({giorni} giorni fa)")
print(f"Manutentori: {manutentori or 'non indicati'}")
print(f"Link: {link or 'nessuno'}")
if giorni < 90 or n_versioni < 3:
print("ATTENZIONE: pacchetto giovane o con poche versioni, controllalo a mano")
def controlla_pypi(nome):
dati = scarica_json(f"https://pypi.org/pypi/{nome}/json")
if dati is None:
return print(f"NON ESISTE su PyPI: {nome}")
date_file = [f["upload_time_iso_8601"][:10] for files in dati["releases"].values() for f in files]
if not date_file:
return print(f"{nome}: esiste su PyPI ma non ha file da installare")
ruoli = dati.get("ownership", {}).get("roles", [])
stampa(dati["info"]["name"], dati["info"]["version"], min(date_file), len(dati["releases"]),
", ".join(r["user"] for r in ruoli), dati["info"].get("project_urls"))
def controlla_npm(nome):
dati = scarica_json(f"https://registry.npmjs.org/{nome.replace('/', '%2F')}")
if dati is None:
return print(f"NON ESISTE su npm: {nome}")
repo = dati.get("repository")
if isinstance(repo, dict):
repo = repo.get("url")
stampa(dati["name"], dati.get("dist-tags", {}).get("latest"), dati["time"]["created"][:10],
len(dati.get("versions", {})), ", ".join(m["name"] for m in dati.get("maintainers", [])), repo)
if __name__ == "__main__":
if len(sys.argv) != 3 or sys.argv[1] not in ("pypi", "npm"):
sys.exit("Uso: python verifica_pacchetto.py pypi|npm nome-pacchetto")
(controlla_pypi if sys.argv[1] == "pypi" else controlla_npm)(sys.argv[2])
Lancialo dalla sua cartella, con il registro e il nome:
Windows:
py verifica_pacchetto.py pypi requests
macOS / Linux:
python3 verifica_pacchetto.py pypi requests
Output del 27 ottobre 2026 (identico su Debian 13 e su macOS 15.7.7):
Nome: requests ultima versione: 2.34.2 versioni: 163
Prima pubblicazione: 2011-02-14 (5734 giorni fa)
Manutentori: Lukasa, graffatcolmingov, nateprewitt
Link: {'Documentation': 'https://requests.readthedocs.io', 'Source': 'https://github.com/psf/requests'}
Con npm al posto di pypi controlli un pacchetto JavaScript: per express lo script ha riportato 290 versioni dal 2010. Per super-fast-pdf-cleaner ha stampato NON ESISTE su entrambi i registri. Su un pacchetto caricato su PyPI quella mattina, con una sola versione, è comparsa la riga ATTENZIONE: non lo nomino, perché nulla indica che sia malevolo. La riga segnala solo che manca una storia da valutare.
Come leggere il risultato:
| Cosa vedi | Esito | Cosa fai |
|---|---|---|
| Anni di storia, molte versioni, repository collegato e nome uguale a quello della documentazione ufficiale | Procedi | Installa dentro l’ambiente virtuale del progetto, con la versione indicata nella documentazione. |
| Esiste, ma è giovane, ha poche versioni o nessun link a un repository | Fermati | Cerca il nome fuori dalla chat: documentazione, repository, chi lo cita. Se non trovi nulla, non installarlo. |
NON ESISTE, E404 o No matching distribution con pagina PyPI inesistente |
Inventato | Chiedi all’IA quale libreria reale svolge quel compito, poi riparti dal controllo 1. |
I download non sono una prova: il pacchetto vuoto di Lasso Security ne ha raccolti più di 30.000 in tre mesi.
Una rete in più: ignorare i pacchetti appena pubblicati
Le versioni più recenti di pip e npm permettono di ignorare i pacchetti pubblicati negli ultimi giorni. Un nome registrato ieri da qualcuno che sfrutta l’IA non viene proprio considerato.
Con pip 26.1 o successivo (opzione --uploaded-prior-to, documentata nella pagina di pip index), P30D significa «caricato almeno 30 giorni fa».
Windows:
py -m pip install --uploaded-prior-to P30D requests
macOS / Linux:
python3 -m pip install --uploaded-prior-to P30D requests
Per npm l’opzione equivalente è --min-release-age, in giorni, descritta nella documentazione di npm install e presente in npm 11.21.1, la versione della prova. Insieme a --ignore-scripts impedisce anche l’esecuzione degli script di installazione dei pacchetti:
npm install express --min-release-age=30 --ignore-scripts
Nella prova entrambi i comandi, lanciati in modalità simulata (--dry-run), hanno funzionato. Con pip index versions requests --uploaded-prior-to P3650D, cioè dieci anni, l’ultima versione mostrata è diventata la 2.11.1 invece della 2.34.2.
🚨 Avviso critico — L’installazione esegue codice: la documentazione di pip lo scrive nella pagina sulle installazioni sicure: installare un pacchetto può eseguire codice arbitrario contenuto nel pacchetto stesso. Su npm accade con gli script
preinstallepostinstall. Per questo i controlli vanno fatti prima del comando di installazione, non dopo.
Errori tipici
ERROR: No matching distribution foundanche per un pacchetto che esiste → il pacchetto non ha versioni per la tua versione di Python, oppure hai scritto male il nome → apri la paginapypi.org/project/nome/e confronta nome e versioni di Python supportate.ERROR: unknown command "index"→ la tua copia di pip è precedente alla 21.2, la versione che ha introdottopip index→ aggiornala dentro l’ambiente virtuale conpy -m pip install --upgrade pipsu Windows opython3 -m pip install --upgrade pipsu macOS e Linux.no such option: --uploaded-prior-to→ pip è precedente alla 26.1 → aggiorna pip come sopra, oppure usa solo i controlli 1, 2 e 3.npm error code 128e un messaggio che citagit ls-remote→ hai scritto un nome con scope senza la chiocciola, per esempiotypes/nodeinvece di@types/node, e npm lo ha letto come un repository GitHub → copia il nome completo dalla documentazione,@incluso.
Domande Frequenti (FAQ)
Basta che pip install vada a buon fine per sapere che il pacchetto è giusto?
No. Se un’installazione riesce, significa solo che un pacchetto con quel nome esiste. Il caso peggiore della package hallucination è proprio questo: il nome inventato dall’IA è stato registrato da qualcun altro e l’installazione va liscia.
Posso chiedere all’IA di verificare da sola se il pacchetto esiste?
Puoi chiederle quale libreria usare e dove trovare la documentazione, ma la verifica va fatta sul registro. Il modello può confermare con sicurezza un nome che non esiste: è lo stesso meccanismo che lo ha portato a inventarlo.
Che differenza c’è tra package hallucination e typosquatting?
Nel typosquatting qualcuno registra un nome simile a una libreria famosa e aspetta un errore di battitura. Nella package hallucination il nome sbagliato lo produce il modello, e chi attacca registra i nomi che i modelli ripetono. La difesa è la stessa: confrontare il nome con la documentazione ufficiale prima di installare.
Vale anche per i pacchetti nel requirements.txt o nel package.json generati dall’IA?
Sì, nome per nome. Un file di dipendenze generato in blocco può contenere un nome inventato tra dieci reali. Lancia lo script su ogni riga che non riconosci; per trovare le righe pip install sparse in README e script c’è lo script greppabile per cercare le 7 falle in un repo.
Se ho già installato un pacchetto sospetto, cosa faccio?
Disinstallarlo non basta, perché il codice di installazione potrebbe essere già stato eseguito. Se lo hai installato dentro un ambiente virtuale, cancella la cartella .venv e ricreala; poi cambia le password e le chiavi API presenti sul computer o nel progetto. Le 7 falle di sicurezza nel codice scritto dall’IA descrivono il contesto della falla 7.
Dopo il controllo
Rendi i tre controlli un’abitudine: nome sul registro, nome confrontato con la documentazione, età e manutentori con lo script. Installa sempre dentro un ambiente virtuale (venv), così un errore resta confinato al progetto. Per i progetti JavaScript, il passo successivo è controllare le dipendenze già installate con la guida su supply chain e npm audit. Se ti serve uno strumento che faccia questi controlli su tutto il requirements.txt in un colpo solo, è uno dei piccoli programmi che realizzo su richiesta. Per gli altri quattro passaggi della revisione, torna al protocollo per il codice generato dall’IA.