← Torna al blog
SicurezzaScanaris Team · 7 min di lettura · October 04, 2026

I segreti che la tua app con Cursor o Lovable sta perdendo nel browser

Quando chiedi a un'IA di "collegare la tua app a Stripe, OpenAI o Supabase", spesso mette la chiave direttamente nel codice del frontend. Funziona al primo colpo, ed è proprio per questo che nessuno controlla. Il problema è semplice e assoluto: tutto ciò che arriva al browser è pubblico. Non c'è modo di nascondere un testo che il browser ha bisogno di leggere. Questa guida spiega perché succede, come un attaccante lo trova in un minuto, quali chiavi sono pericolose e quali no, come cercarle da solo e cosa fare se una è già trapelata.

Perché la chiave finisce nel browser

Ci sono tre strade abituali. La prima è il prefisso pubblico delle variabili d'ambiente: in Next.js tutto ciò che inizia con NEXT_PUBLIC_ viene incorporato nel JavaScript del client, come VITE_ in Vite, NUXT_PUBLIC_ in Nuxt o REACT_APP_ in Create React App. L'IA vede che "la variabile non arriva" e le aggiunge il prefisso perché arrivi. La seconda è la chiamata diretta: l'assistente scrive fetch('https://api.openai.com/...') in un componente dell'interfaccia con la chiave nell'header, perché è l'esempio più corto che conosce. La terza è un file di costanti condiviso, importato sia dal server che dal client, dove qualcuno ha lasciato la chiave "temporaneamente".

Nei tre casi il risultato è lo stesso: la chiave viaggia in un file .js che chiunque può scaricare, e resta nella cache dei CDN, nella cronologia del browser dei tuoi utenti e negli archivi dei crawler che percorrono internet cercando esattamente quello.

Come lo vede un attaccante in 60 secondi

  • DevTools. Apri il tuo sito, premi F12, vai nella scheda Sources, premi Ctrl+Maiusc+F per cercare in tutti i file e scrivi sk_live, sk-ant, AKIA, service_role o -----BEGIN. Se compare, ce l'ha già.
  • Codice sorgente. Clic destro, visualizza sorgente, e apri ogni file .js collegato. I bundle sono minificati ma non cifrati; una ricerca di testo basta.
  • Source map. Se pubblichi i .map, oltre al codice vede i nomi originali delle variabili e i commenti, compresi i // TODO: spostare sul server.
  • Scanner automatici. Ci sono bot che percorrono i domini nuovi scaricando il JavaScript e provando ogni pattern di chiave contro il fornitore corrispondente. Una chiave AWS trapelata in un bundle viene usata in minuti, non in giorni.

Quali chiavi sono pericolose e cosa succede se trapelano

  • Stripe sk_live_. Permette di creare addebiti, rimborsi e leggere i dati dei clienti. È denaro vero.
  • OpenAI, Anthropic e simili (sk-…). Chi ce l'ha consuma sul tuo conto. Le fatture da migliaia di euro per una chiave in un repository pubblico sono una storia che si ripete.
  • AWS AKIA… e Google Cloud. A seconda dei permessi: accesso a bucket, database e creazione di macchine per minare a tue spese.
  • Supabase service_role. Bypassa completamente il Row Level Security: lettura e scrittura totale del tuo database. La più grave di tutte nelle app vibe-coded.
  • Account di servizio Firebase (JSON). Equivalente del service_role: controllo totale del progetto.
  • Fornitori di email e SMS (Resend, SendGrid, Twilio). Inviare email e messaggi a tuo nome, cioè phishing con il tuo dominio.
  • Stringhe di connessione al database con utente e password, e segreti JWT: con il segreto si firmano sessioni di qualsiasi utente.
  • Token di accesso salvati in localStorage. Non sono chiavi tue ma dei tuoi utenti, e qualsiasi XSS li legge e li ruba.

Quali chiavi possono andare nel client

Non ogni chiave è un segreto. Alcune sono progettate per vivere nel browser, purché ciò che sta dietro sia configurato bene:

  • La chiave anon o publishable di Supabase, protetta dalle policy RLS di ogni tabella.
  • La chiave pubblica di Stripe (pk_live_), che serve solo ad avviare pagamenti, non a incassarli.
  • La configurazione di Firebase (apiKey, projectId…), protetta dalle regole di sicurezza e, meglio, da App Check.
  • Una chiave di Google Maps limitata per dominio dalla console di Google.
  • Identificativi di analytics e di widget di terze parti.

La regola per distinguerle: una chiave può andare nel client se il fornitore la chiama esplicitamente pubblica e se, pur avendola, uno sconosciuto non può fare nulla che tu non voglia. Se hai dubbi, è segreta.

Come cercarle da solo

Nel bundle già costruito, cerca pattern noti. Con Next.js o Vite, dopo la build:

grep -rEo "sk_live_[0-9a-zA-Z]+|sk-ant-[0-9a-zA-Z-]+|AKIA[0-9A-Z]{16}|service_role|eyJhbGciOi[0-9a-zA-Z._-]+" .next/static dist 2>/dev/null | head

Nel repository, prima che arrivi al bundle, strumenti come gitleaks o trufflehog percorrono l'intera cronologia, non solo l'ultimo commit. E nel pannello del tuo hosting, controlla quali variabili hanno prefisso pubblico: ognuna di esse viene inviata al browser.

Tre posti dove la gente non guarda: i componenti marcati 'use client' che importano un file di configurazione condiviso; i parametri dell'URL, dove a volte viaggia un token che finisce nei log e nella cronologia; e i repository pubblici su GitHub, dove la chiave può stare da mesi anche se non è più nel codice attuale.

Come correggere: la chiave resta sul server

Lo schema corretto è che il browser parli con il tuo server e il tuo server parli con il fornitore. Un endpoint tuo riceve la richiesta, controlla la sessione dell'utente, applica un limite d'uso e fa la chiamata con la chiave letta da una variabile d'ambiente senza prefisso pubblico. In Next.js:

// app/api/summarize/route.ts  (solo server)
import { getSession } from '@/lib/auth'

export async function POST(req: Request) {
  const session = await getSession(req)
  if (!session) return new Response('Unauthorized', { status: 401 })

  const { text } = await req.json()
  if (typeof text !== 'string' || text.length > 5000) {
    return new Response('Bad request', { status: 400 })
  }

  const r = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.OPENAI_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: text }] }),
  })
  return new Response(r.body, { headers: { 'Content-Type': 'application/json' } })
}

Con Supabase, stessa idea: le operazioni che hanno bisogno di service_role vanno in una Edge Function o nel tuo backend, mai nel client. Con Firebase, in una Cloud Function. E in ogni caso la variabile d'ambiente si chiama OPENAI_API_KEY, non NEXT_PUBLIC_OPENAI_API_KEY.

Per i token di sessione, preferisci i cookie HttpOnly a localStorage: un cookie HttpOnly non è leggibile da JavaScript, quindi un XSS non può rubarlo. Se la tua libreria salva la sessione in localStorage di default, come fa il client Supabase nel browser, compensa con una CSP rigorosa o usa la sua variante server con i cookie.

Se è già trapelata: ruotare, non cancellare

Togliere la chiave dal codice non serve a nulla: è già nelle cache, nella cronologia di git e, probabilmente, in mano a qualcuno. L'ordine corretto è:

  • Creare una chiave nuova presso il fornitore e distribuirla sul server.
  • Revocare quella vecchia subito dopo. In Stripe, OpenAI e AWS si fa dalla dashboard con un clic.
  • Controllare l'uso: fatturazione, log delle API e addebiti degli ultimi giorni. Se c'è stato un abuso, apri un ticket con il fornitore; a volte annullano gli addebiti.
  • Impostare limiti di spesa e avvisi presso ogni fornitore che lo permette, così la prossima volta fa meno male.
  • Se la chiave dava accesso a dati personali di utenti, valuta se devi notificarlo ai sensi del GDPR.

Pulire la cronologia di git con strumenti come BFG o git filter-repo è buona igiene, ma non sostituisce la rotazione.

Come lo verifica Scanaris

Scanaris scarica il JavaScript che il tuo sito serve, proprio come farebbe un attaccante, e cerca pattern di chiavi dei fornitori abituali, token JWT, URL di webhook e stringhe di connessione. Avvisa anche se le tue source map sono raggiungibili e se ci sono file sensibili a portata di mano. Ogni risultato arriva con un prompt da incollare nella tua IA con la correzione esatta: spostare la chiamata sul server e ruotare la chiave. Se attivi il monitoraggio, ti avvisa quando una nuova sessione di vibe coding rimette una chiave nel client. Fa parte della checklist di lancio e della guida alla sicurezza per app create con l'IA.

Domande frequenti

La chiave anon di Supabase è un segreto?

No. È pensata per il browser e la protegge l'RLS di ogni tabella. Quello che è segreto è service_role. Se il tuo RLS è sbagliato, il problema non è la chiave anon ma le policy.

E se la chiave è limitata per dominio?

Le restrizioni per referer (come in Google Maps) riducono l'abuso da altri siti, ma non da script che falsificano l'header. Vanno bene per chiavi a basso rischio, non per chiavi che spendono soldi o leggono dati.

Basta cancellarla dal repository?

No. Una chiave che è stata in un bundle pubblico o in un commit pubblico è compromessa. Ruotala sempre, e poi pulisci la cronologia se vuoi.

Come se la cava il tuo sito?

Lascia che Scanaris lo analizzi e verifica in 30 secondi, gratis.

Scansiona il mio sito

Controlli correlati

Guide di sicurezza per stack