Segreti, .env e chiavi API nei progetti vibe-coded: dove finiscono e come fermarle

Nei progetti vibe-coded le chiavi API finiscono nel bundle client, nella storia Git e nei log di CI. Ecco dove cercarle, come ruotarle e quali controlli mettere prima del push — senza inventare incidenti in prima persona.

Francesco Pio Nocerino7 minSviluppo
PercorsoVibe codingParte 17 di 24
Segreti, .env e chiavi API nei progetti vibe-coded: dove finiscono e come fermarle
Indice

Questo pezzo è per chi genera un'app con un agente o un tool di vibe coding e si chiede dove finiscono davvero le chiavi API. Non è un diario di incidenti personali. È una procedura costruita sulla documentazione ufficiale (GitHub, Next.js, Vite, Cloudflare) e su un report pubblico su app vibe-coded, verificati il 14 ottobre 2026.

La checklist per portare in produzione un sito vibe-coded già dice: nessun secret nel frontend, nessuna credenziale nella storia Git. Qui si scende nel dettaglio: dove leakano, come trovarle, come ruotarle, cosa mettere prima del push.

In breve:

  • Le chiavi private non devono mai avere il prefisso NEXT_PUBLIC_ o VITE_: quei valori finiscono nel JavaScript del browser.
  • Togliere un .env dal working tree non basta: se è stato committato, resta nella storia del repository.
  • GitHub push protection e scanner locali (Gitleaks, TruffleHog) bloccano o segnalano secret prima che diventino un incidente.
  • Se una chiave è uscita, si ruota dal pannello del provider e si invalida quella vecchia: cancellare il file non chiude l'accesso.
  • Su Cloudflare Workers i secret di produzione passano da wrangler secret put; in locale da .dev.vars, fuori da Git.

Il problema non è "funziona in demo"

Escape, nel report The State of Security of Vibe Coded Apps, ha analizzato oltre 5.600 applicazioni pubbliche costruite con piattaforme di vibe coding. Tra i risultati: più di 2.000 vulnerabilità ad alto impatto, oltre 400 secret esposti e 175 casi di dati personali accessibili. La metodologia descrive anche JWT e token Supabase recuperabili dai bundle JavaScript del frontend.

Non è un edge case da blog security. È il modo in cui molti progetti generati dall'AI arrivano online: il modello mette la chiave dove "funziona", e funzionare nel browser è il percorso più corto.

La domanda utile non è "l'AI è sicura?". È: quali percorsi fanno uscire una chiave, e quali controlli li chiudono prima del deploy.

Dove finiscono le chiavi

Sei posti ricorrenti. Li controlli in questo ordine.

1. Bundle client (il più comune)

Next.js, Vite e la maggior parte dei bundler moderni espongono al browser solo le variabili con un prefisso pubblico. Il resto resta sul server — a patto che non lo importi in un componente client.

La documentazione Next.js sulle environment variables è esplicita: le variabili con prefisso NEXT_PUBLIC_ vengono *inlineate* nel JavaScript inviato al browser al momento del next build. Non sono "lette a runtime" dal client: diventano stringhe fisse nel bundle.

Vite fa lo stesso con il prefisso VITE_. La guida Env Variables and Modes avverte: le variabili VITE_* non devono contenere informazioni sensibili; i valori finiscono nel codice sorgente del client al build. Per i secret serve un backend o una funzione server/edge.

Se un agente scrive NEXT_PUBLIC_OPENAI_API_KEY o VITE_STRIPE_SECRET_KEY "perché così funziona nel componente", la chiave è pubblica. Nessun .gitignore la salva: è nel file JS servito a chiunque.

2. File .env committato

Il classico. Il tool genera .env con le chiavi vere, qualcuno fa git add ., il file entra nel primo commit. Anche se lo togli al commit successivo, la storia lo conserva: git log -p -- .env o un clone completo lo recuperano.

I controlli che conviene fare:

  • .env, .env.*, .dev.vars* e *.pem in .gitignore prima del primo commit utile
  • uno scan sulla storia, non solo sul tree corrente
  • se il repo è già pubblico con un secret dentro, tratta la chiave come bruciata (vedi rotazione sotto)

3. Storia Git e fork

Anche un repo privato con collaboratori espone le chiavi a chi ha clone. Un fork, un mirror, un backup su un altro host: la storia viaggia. Rimuovere il file dall'ultimo commit non riscrive i precedenti. Per ripulire la storia servono rewrite (git filter-repo o equivalenti) *e* rotazione della chiave: chi ha già clonato tiene la copia.

4. Log di CI e output di build

GitHub Actions, Workers Builds, Vercel: se uno step stampa env o un errore include la variabile, il secret finisce nei log. I log sono spesso accessibili a più persone del repo, a volte pubblici su fork o su artifact.

Regola operativa: non fare echo di secret, non passare chiavi come argomenti di comando visibili in ps, usare i secret store della piattaforma (GitHub Actions secrets, wrangler secret, dashboard del vendor).

5. Screenshot, issue e chat dell'agente

Una captura del terminale con cat .env, un paste in un issue GitHub, un messaggio in una chat con l'agente di coding: sono canali di leak. Alcuni tool di agent tengono cronologia cloud. Non serve un attacco sofisticato: basta un link condiviso.

In parallelo, attenzione alle dipendenze inventate dall'AI: un npm install su un pacchetto allucinato può rubare variabili d'ambiente in fase di install. Ne ho scritto in slopsquatting e controlli prima di npm install.

6. Config di piattaforma scritta in chiaro

Su Cloudflare Workers, mettere un token in vars nel file Wrangler lo lascia in chiaro nel repository. La documentazione sui secrets indica wrangler secret put (o wrangler versions secret put) per la produzione, e .dev.vars / .env solo in locale, fuori da Git.

Nella migrazione Pages → Workers le variabili di build e di runtime non sono più condivise come su Pages: è un altro punto in cui le chiavi finiscono nel posto sbagliato se le copi "come prima".

Prefissi pubblici: la regola in una tabella

Contesto

Prefisso / meccanismo

Finisce nel client?

Uso corretto

Next.js

NEXT_PUBLIC_*

Sì, inline al build

Solo valori pubblici (URL sito, chiave *publishable* Stripe, ID analytics)

Next.js

senza prefisso

No (solo server)

Secret, DB URL, token privati

Vite

VITE_*

Sì

Stesso criterio: solo dati già pubblici

Vite

senza VITE_

No

Secret e config server

Cloudflare Workers

vars in Wrangler

Nel repo (in chiaro)

Config non sensibile

Cloudflare Workers

wrangler secret / .dev.vars

No (se .dev.vars è ignorato)

Token e chiavi private

Se il modello ti propone di "esporre" una chiave OpenAI, Anthropic, Stripe secret, database o service role Supabase con un prefisso pubblico, rifiuta lo schema. La chiave pubblica di Supabase (anon) può stare sul client *solo* se le policy RLS tengono; la service_role no. La checklist vibe-coded insiste su RLS proprio per questo.

Come trovarle (prima e dopo il push)

Sul tree e sulla storia locale

Gitleaks scansiona repository Git, directory e stdin. Comandi tipici: gitleaks git sulla storia, gitleaks dir sui file. Utile in pre-commit e in CI.

TruffleHog classifica oltre 800 tipi di secret e, dove possibile, li verifica contro le API del provider (--only-verified riduce i falsi positivi). Serve quando vuoi sapere se una chiave trovata è ancora attiva.

Esempio minimo in CI (adatta l'immagine/tag alla tua pipeline):

- name: Scan secrets
  run: |
    gitleaks git --verbose --redact

Non sostituisce la review umana, ma intercetta pattern ovvi (AWS, GitHub PAT, OpenAI, Slack, ecc.).

Su GitHub: secret scanning e push protection

La push protection di GitHub blocca i push che contengono secret supportati *prima* che entrino nel repository. Vale per CLI, commit dall'UI, upload di file, alcune chiamate REST API e, sui repository pubblici, interazioni con il GitHub MCP server.

Due modalità utili da conoscere:

  • Per utente (su GitHub.com): attiva di default, limita i push di secret verso repository *pubblici*.
  • Per repository: va abilitata (richiede Secret Protection); blocca i push verso quel repo e genera alert se qualcuno bypassa con un motivo.

Se push protection ti ferma, non "bypass subito per finire il task". Rimuovi il secret, riscrivi il commit, riprova. Il bypass lascia traccia e, se il secret era vero, resta comunque da ruotare.

Sul bundle deployato

Dopo il build, cerca stringhe sospette nel output statico (dist/, .next/static/, asset Workers). Un rg -i 'sk-|api_key|BEGIN PRIVATE' dist non è elegante, ma intercetta errori grossolani prima del go-live. Escape ha trovato molti token proprio nei bundle frontend delle app vibe-coded.

Come ruotare (quando la chiave è già uscita)

1. Invalida sul provider (OpenAI, Stripe, GitHub, Cloudflare, Supabase…): genera una nuova chiave e revoca la vecchia. Non invertire l'ordine se puoi evitarlo: una chiave ancora valida nel wild resta usabile. 2. Aggiorna i secret store (dashboard hosting, wrangler secret put, GitHub Actions secrets), non i file nel repo. 3. Verifica accessi: log del provider, spend limit, rate limit. Se la chiave poteva scrivere (deploy, pagamenti, admin DB), controlla audit log e risorse create di recente. 4. Ripulisci la storia solo dopo aver ruotato: un rewrite senza rotazione lascia chiavi vive nei clone altrui. 5. Limita il blast radius: chiavi con scope minimo, spend cap, IP allowlist dove c'è. Su chiamate LLM, un AI Gateway con log, cache e tetto di spend riduce il danno di una chiave abusata. Sulle API pubbliche, il rate limiting su Workers non sostituisce la rotazione, ma frena l'abuso.

Controlli da fare prima del push

Checklist operativa, da ripetere ad ogni repo generato con AI:

  • .gitignore include .env, .env.*, .dev.vars*, chiavi PEM e cartelle di credenziali del tool
  • nessuna variabile NEXT_PUBLIC_ / VITE_ contiene secret
  • i secret di produzione non sono in vars Wrangler né in file JSON committati
  • gitleaks (o equivalente) gira in locale o in CI sulla storia
  • push protection attiva sul repo (e non bypassata "per fretta")
  • lo scan sul bundle di produzione non mostra token
  • le dipendenze proposte dall'AI sono state verificate (niente install cieco)

Se stai scegliendo ancora lo stack, evita di ereditare default pericolosi: la trappola dello stack di default AI/Next spiega perché "quello che genera il modello" non è un'architettura. L'hub Vibe coding raccoglie gli altri pezzi del filone.

Cosa conviene fare adesso

1. Lancia uno scan sulla storia del repo (gitleaks git o TruffleHog) e sul tree corrente. 2. Cerca NEXT_PUBLIC_ e VITE_ nel codice: ogni valore deve essere pubblicabile senza danni. 3. Abilita push protection sul repository e tieni i secret fuori da Git. 4. Se trovi una chiave viva, ruotala sul provider prima di ogni altra pulizia. 5. Rileggi la checklist vibe-coded per RLS, auth server-side e il resto del go-live.

Il vibe coding accelera il prototipo. I secret non perdonano la stessa velocità: o li tratti come vincolo di progetto, o li ritrovi nel bundle.

[ DOMANDE FREQUENTI ]

Domande frequenti

01Basta cancellare il .env e fare un nuovo commit?

No. Il file resta nei commit precedenti. Serve uno scan sulla storia, la rotazione della chiave, e solo dopo un eventuale rewrite della history se il repo era già condiviso.

02NEXT_PUBLIC_ con una chiave "di test" è accettabile?

Se la chiave apre risorse reali (anche in sandbox con dati o quota), no. Le chiavi di test si gestiscono come secret se permettono azioni o consumano credito. Nel client metti solo ciò che è già progettato per essere pubblico.

03Push protection sostituisce Gitleaks?

No. Push protection copre pattern che GitHub riconosce e si attiva al push verso GitHub. Gitleaks/TruffleHog girano in locale, su altre host Git, e su file che non hai ancora pushato. Usali insieme.

04Dove metto i secret su un Worker Cloudflare?

In produzione: npx wrangler secret put NOME_CHIAVE (o la variante versions secret put se usi gradual deployments). In locale: file .dev.vars accanto al Wrangler config, aggiunto a .gitignore. Non duplicare i valori in vars nel file di config.

Condividi