@ :~/lab/npmshield-impero-demolito-da-un-agente$
← cd ~/lab

./run npmshield-impero-demolito-da-un-agente

NPMShield: ho disegnato un impero da 7 database, un agente me l'ha raso al suolo

Come ho sketchato in Claude Code una piattaforma anti-malware per npm — sandbox, AI, 7 database — e come una revisione avversariale l'ha ridotta a una CLI e una GitHub App.

status
draft
project
npm-shield
updated
2026-07-07
tags
#npm#supply-chain#malware#static-analysis#sandbox#claude-code
generate cover # alt: Diagramma di una pipeline di analisi pacchetti npm a due livelli: verdetto statico rapido e verdetto dinamico asincrono in sandbox

Il primo file che ho scritto per NPMShield non era codice. Era un BOOTSTRAP.md che diceva a Claude Code, in sequenza: costruisci il core engine, poi il layer di intelligence, poi la CLI, poi l’API, poi la dashboard, poi il deploy su Kubernetes. Sei fasi, un impero. Il secondo file che ho scritto, tre settimane dopo, era un verdetto di una parola: PIVOT. Questo diario racconta cosa è successo nel mezzo — e perché il momento più utile del progetto è stato quando un agente ha demolito il mio piano.

L’idea (quella buona, che è rimasta)

Il malware nella supply chain npm non è più un problema teorico. Shai-Hulud 2.0, a novembre 2025, ha compromesso 796 pacchetti per 20 milioni di download settimanali, propagandosi su oltre 25.000 repository GitHub. CanisterWorm — attivo mentre scrivo — usa la blockchain ICP come canale di comando e controllo: niente dominio da sequestrare, niente takedown possibile. Il pattern comune è codice malevolo dentro pacchetti legittimi, in hook postinstall che spediscono process.env a un server esterno. Gli scanner CVE non lo vedono: non c’è una vulnerabilità nota, c’è un comportamento.

L’idea è un antivirus per l’era della supply chain: intercettare quel comportamento prima dell’install, con analisi statica, sandbox dinamica e threat intelligence assistita da Claude.

Il bootstrap: sketchare tutto prima di scrivere niente

L’approccio che ho usato — e che rifarei — è quello di far disegnare l’intero sistema a Claude Code prima di scrivere una riga di codice vero. Ogni modulo nasce come un BOOTSTRAP.md con obiettivo, stack e file da creare, seguito da un’invocazione:

cd core
claude code "Costruisci il core analyzer. Parti dallo StaticAnalyzer:
parsing di package.json, rilevamento di postinstall sospetti,
download esterni, modifiche di sistema."

Il cuore tecnico si regge su tre pezzi in fila:

  1. StaticAnalyzer — AST con tree-sitter (via py-tree-sitter) più regole Semgrep e query custom .scm. I pattern che contano sono pochi e concreti: eval(), Function(), child_process.exec, fs.writeFileSync verso .bashrc, require() dinamico di stringhe decodificate da base64, esfiltrazione di process.env. GuardDog come baseline fa 93,32% di F1; combinando le tecniche si arriva verso il 96%.
  2. DynamicSandbox — esecuzione dell’install in isolamento, con osservazione di syscall e traffico di rete.
  3. ThreatIntel — Claude che sintetizza il verdetto finale a livello di pacchetto, dove la qualità del ragionamento serve davvero.

Fin qui, sensato. Il problema è stato tutto il resto che ci avevo appiccicato attorno.

La decisione dura: quando il piano è crollato

Prima di scrivere codice ho fatto girare una fase di ricerca e validazione — quattro dossier (mercato, dominio, fattibilità tecnica, revisione avversariale) più una sintesi, tutti prodotti da agenti dentro Claude Code. La revisione avversariale è stata brutale e aveva ragione.

Il mio piano originale era: 14 feature, 7 database, multi-linguaggio, AI-ovunque, founder solo, 8 settimane, contro tre incumbent finanziati (uno con $65M raccolti, un altro con $70M, un terzo valutato $1B). Verdetto: combinazione con tasso storico di successo prossimo allo zero. Non “Kill”, non “Ship”. Pivot: tieni il problema, sventra la soluzione.

Da quel taglio è nata la parte più preziosa: il wedge, la fessura che nessun concorrente presidia. Si chiama cross-package publish fan-out velocity correlation. Quando un singolo publisher npm spinge patch su N pacchetti nel giro di minuti, quella è la firma di un worm. Nessuno scanner single-package la vede — Socket, Snyk, GuardDog, Phylum analizzano un pacchetto alla volta. Sedendo sul lato consumer (la macchina che fa l’install) e aggregando la telemetria di pubblicazione tra i clienti, quel correlatore avrebbe intercettato Shai-Hulud 2.0 ore prima della moderazione del registry.

Tutto il resto è finito nella lista “tagliato da MVP-0”: dashboard web, FalkorDB, pgvector, eBPF/Tetragon, Firecracker, federated learning, self-hosted enterprise, Kubernetes. MVP-0 è ora una CLI in Go e un servizio FastAPI su una singola VM con Docker Compose. E c’è un kill switch onesto scritto nero su bianco: se entro 12 settimane dal lancio non raggiungo 100 sviluppatori attivi a settimana, il progetto si chiude.

Il numero che era una bugia fisica

La spec originale prometteva <200ms full scan. La fase di fattibilità l’ha smontata con la fisica: una microVM Firecracker con snapshot restora in ~28ms, ma uno scan vero include fetch del tarball (50–500ms di rete), estrazione, parsing, e per il dinamico un npm install con cattura delle syscall. Non entra in 200ms. Punto.

La correzione è stata separare in due livelli e riscrivere lo SLO pubblico:

verdetto statico:  < 200ms p95   (sincrono, cache-hit: AST + euristiche)
verdetto dinamico: < 30s p95      (asincrono, via WebSocket, sandbox)

Corollario: Claude non può stare nel percorso caldo dei 200ms. Haiku ha un TTFT di ~300ms, un verdetto da 500 token è ~3s. Claude vive nel livello dinamico, non in quello sincrono.

Gotcha onesti

  • README e realtà divergono, e va bene così. L’IDEA.md è un pitch entusiasta con business plan, exit strategy e roadmap di funding. Il CLAUDE.md successivo lo marca esplicitamente come “contesto storico, non autoritativo”. Ho lasciato entrambi: la storia del pivot è più credibile del piano ripulito.
  • Anthropic non ha un’API di embeddings. L’avevo dato per scontato in tre punti del piano. Non esiste: la cookbook ufficiale rimanda a Voyage AI. Uso voyage-code-3 a 512 dimensioni, int8 quantizzato — a $0.18 per milione di token, fare il backfill dell’intero registry npm costa circa $27. Praticamente gratis.
  • La licenza che avvelena il SaaS. FalkorDB è SSPLv1: in un SaaS ti obbliga ad aprire l’intero stack. Decisione: FalkorDB solo nell’edizione self-hosted (dove il cliente è l’operatore), Postgres + Apache AGE nel SaaS. E per MVP-0/1, nessun graph DB: una CTE ricorsiva in Postgres regge sotto il milione di nodi.
  • L’ironia del postinstall. Uno strumento anti-supply-chain distribuito via npm con un postinstall può diventare il vettore di compromissione. Regola ferrea: ogni release firmata con Sigstore, superficie del postinstall ridotta al download di un binario con checksum verificato. E la valutazione seria di non distribuire affatto via npm.
  • Il costo dell’AI che scappa. Sonnet 4.6 su un pacchetto costa ~$0.020 con prompt caching. La strategia è Haiku-first, escalation a Sonnet solo sotto 0.85 di confidenza: costo misto ~$8 ogni 1000 scan, con circuit breaker verso un modello locale al superamento del budget giornaliero.

Come va

Onestà da diario: non c’è ancora codice. NPMShield è in fase di planning/POC. Quello che esiste è la ricerca sintetizzata, le decisioni bloccate in un CLAUDE.md, e una roadmap BMAD che porta da product brief a PRD ad architettura a implementazione. La prossima azione concreta non è “scrivi lo StaticAnalyzer” — è formalizzare il brief di MVP-0 e firmare tre design partner prima di toccare la tastiera. Il mio artefatto di test primario, quando il codice arriverà, sarà un comparison harness che gira ogni notte contro Socket, Snyk e npm audit su corpus pubblici di malware.

Cosa ho imparato

Che il valore del bootstrap in Claude Code non è la velocità con cui produce codice — è la velocità con cui produce un piano abbastanza dettagliato da poter essere demolito. Sketchare l’impero da 7 database è servito: senza quel piano completo, la revisione avversariale non avrebbe avuto niente di specifico da attaccare. Il pivot non è nato dal ridimensionare le ambizioni, ma dall’aver reso le ambizioni concrete al punto da poterle falsificare.

E la lezione più contenta di essere impopolare: la cosa più utile che ho scritto per un progetto ambizioso è stata la condizione per cui va chiuso. Un kill switch a 100 utenti in 12 settimane vale più di dieci pagine di go-to-market. Se sei da solo contro chi ha raccolto ottanta milioni, la disciplina di cosa non costruire è l’unico vantaggio competitivo che ti puoi permettere.