Tutto ciò che gira nel browser è, per costruzione, disponibile al browser — e quindi a chi lo controlla. Non c'è configurazione, licenza o tecnica che cambi questo fatto: se il codice deve essere eseguito sulla macchina di qualcun altro, quella macchina deve poterlo leggere.

Detto questo, la conclusione «allora non c'è niente da fare» è sbagliata. C'è una differenza sostanziale fra ciò che si è deciso di esporre e ciò che si espone per distrazione, e la seconda categoria è più ampia di quanto si creda.

Il perimetro reale#

CosaProteggibile?Come
Logica dell'interfaccianoil browser la esegue: leggibile per definizione
Sorgenti leggibili e commentatinon pubblicarli: struttura delle cartelle
Source mapnon generarle in produzione, o tenerle fuori dalla cartella servita
File di progetto (package.json, .git, .env)fuori dalla radice pubblica, più una guardia esplicita
Chiavi API e segretimai, se spediti al clientnon spedirli: proxy sul vostro server
Logica di business e regole, se sta sul serveril client chiede, il server decide
Algoritmi proprietarisolo lato serverun endpoint che restituisce il risultato, non il metodo

La riga che conta è la penultima. La differenza fra un frontend che è una vetrina della logica e uno che è una superficie di rendering non è una questione di offuscamento: è una decisione di architettura, e determina quanto c'è da copiare.

La difesa strutturale#

server.jsJavaScript
// La regola strutturale: i sorgenti non stanno dentro la cartella servita.// Non "sono bloccati": non sono lì. È una differenza importante, perché// una regola si può disattivare per sbaglio, una cartella no.////   src/js, src/css        ← sorgenti, mai serviti//   public/js-build        ← bundle offuscato, servito//   public/css-build       ← css minificato, servitoconst PUBLIC_DIR = path.join(__dirname, 'public');// La guardia serve al caso in cui la struttura sia giusta ma il SERVER no:// una copia vecchia rimasta su disco dopo un rsync senza --delete, una// cartella ripristinata a mano da un backup, un collega che ha fatto// una prova. Blocca i percorsi storici indipendentemente da cosa c'è lì.app.use(['/js', '/css', '/src'], (req, res) => notFound(res));app.use('/js-build', express.static(path.join(PUBLIC_DIR, 'js-build'), {  maxAge: '365d', immutable: true}));app.use(express.static(PUBLIC_DIR, { index: false, dotfiles: 'ignore' }));

Il punto è la distinzione fra «bloccato» e «non c'è». Una regola che blocca /src è una riga che qualcuno può cancellare o riordinare; una struttura in cui i sorgenti stanno fuori dalla radice pubblica non ha niente da bloccare. La guardia esplicita resta comunque utile, ma per un motivo diverso: copre il caso in cui il server abbia in mano qualcosa che il repository non ha più — una cartella vecchia sopravvissuta a un rsync senza --delete, un ripristino parziale da backup.

La riga che annulla tutto#

build.jsJavaScript
// L'errore che annulla tutto il lavoro, in una riga di configurazione.// La source map è la mappa che riporta il bundle al sorgente originale:// commenti, nomi delle variabili, struttura dei file. Pubblicarla accanto// al bundle significa pubblicare il sorgente con un passaggio in più.esbuild.build({  entryPoints: ['src/js/app.js'],  bundle: true,  sourcemap: false        // ← in produzione: false, o 'external' su un});                       //   percorso NON servito// Il browser la trova da solo: in fondo al bundle c'è un commento//     //# sourceMappingURL=app.js.map// e DevTools la scarica senza che nessuno gliela chieda.

Se le source map servono per il monitoraggio degli errori in produzione — ed è un'esigenza legittima — la soluzione non è pubblicarle: è caricarle sul servizio di error tracking, che le usa lato suo per simbolizzare gli stack trace senza esporle a nessuno.

Verificare invece di supporre#

controllo post-deploy
# Cinque secondi per sapere se i sorgenti sono online. curl vede quello# che vede un bot: fa la richiesta e stampa il codice, senza eseguire JS.for p in /js/app.js /src/js/app.js /css/base.css /js-build/app.js.map \>          /.git/config /.env /package.json /server.js; do>   printf '%-26s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "https://esempio.it$p")"> done/js/app.js                 404/src/js/app.js             404/css/base.css              404/js-build/app.js.map       404/.git/config               404/.env                      404/package.json              404/server.js                 404# Qualunque 200 in questa lista e' un problema, non una curiosita'.
Vale la pena farne uno script e lanciarlo dopo ogni pubblicazione: le configurazioni cambiano, e questo tipo di regressione non produce alcun sintomo visibile.

La cosa che non si può proteggere#

Qui il malinteso è comune e costoso. L'offuscamento rende il codice illeggibile; non rende i dati irrecuperabili. E una credenziale che il browser deve usare è una credenziale che il browser deve poter ricostruire.

illusione.jsJavaScript
// Il malinteso più costoso: "la chiave è offuscata, quindi è protetta".// L'offuscamento rende il codice illeggibile, non i dati irrecuperabili.// Il browser DEVE poter usare la chiave, quindi la chiave DEVE essere// ricostruibile in memoria — e ci si arriva senza leggere una riga.const CHIAVE = decodifica(_0x4a2f[17]);   // sembra protettafetch('https://api.esempio.it/v1/dati', {  headers: { 'Authorization': 'Bearer ' + CHIAVE }});
estrazione
# Come si estrae, senza deoffuscare niente: si guarda cosa parte.# Pannello Rete, filtro Fetch/XHR, e la chiave e' nell'intestazione# della richiesta, in chiaro, perche' deve esserlo per funzionare.Request Headers  Authorization: Bearer sk_live_9f3c2a...# Oppure, senza nemmeno aprire il pannello: un punto di interruzione# su fetch, e si legge l'argomento.# In console:>  const _f = fetch;>  window.fetch = (...a) => { console.log(a); return _f(...a); };# Tempo richiesto: trenta secondi.
Non è stato deoffuscato niente. Non serve: la chiave è visibile nel momento in cui viene usata, ed è per quello che è lì.

Vale per le chiavi API, per i token di servizio, per le credenziali di un database consultato dal client, per le password «di controllo» confrontate in JavaScript. La regola non ha eccezioni: ciò che arriva al browser è pubblico, indipendentemente da come è stato impacchettato.

La forma corretta: il proxy#

proxy.jsJavaScript
// ✓ La forma corretta: la credenziale non attraversa mai il browser.//   Il frontend chiama VOI, voi chiamate il servizio esterno.app.post('/api/traduci', autenticato, limitaRichieste, async (req, res) => {  const testo = String(req.body.testo || '').slice(0, 5000);   // limite  if (!testo) return res.status(400).json({ errore: 'testo mancante' });  const r = await fetch('https://api.esempio.it/v1/traduci', {    method: 'POST',    headers: {      // La chiave sta qui, sul server, in una variabile d'ambiente.      'Authorization': 'Bearer ' + process.env.CHIAVE_TRADUZIONE,      'Content-Type': 'application/json'    },    body: JSON.stringify({ testo, lingua: 'it' })  });  if (!r.ok) return res.status(502).json({ errore: 'servizio non disponibile' });  res.json(await r.json());});// Guadagno secondario, spesso più importante del primo: adesso avete un// punto in cui contare, limitare, registrare e — se il fornitore fattura// a consumo — impedire che qualcuno vi spenda il budget in una notte.

Il beneficio secondario è spesso quello che convince: adesso esiste un punto in cui contare le chiamate, applicare un limite di frequenza, registrare chi ha usato cosa, e — se il fornitore fattura a consumo — impedire che una chiave finita in giro vi svuoti il budget in una notte. Una chiave nel frontend non ha nessuna di queste proprietà, e non ha nemmeno un modo di essere revocata senza pubblicare una nuova versione del sito.

Quanto costa l'offuscamento, e cosa compra#

build.cjsJavaScript
// Che cosa fa davvero un offuscatore, e che cosa costa.{  compact: true,  controlFlowFlattening: true,        // +costo runtime, il più efficace  controlFlowFlatteningThreshold: 0.5,  deadCodeInjection: true,            // +peso del file  stringArray: true,                  // le stringhe non si cercano più  stringArrayEncoding: ['base64'],  stringArrayThreshold: 0.75,  selfDefending: true,                // il codice si rompe se riformattato  debugProtection: false              // ← lasciato a false: vedi sotto}// Il prezzo è reale e va messo in conto://   - il file cresce (nel nostro caso: 17 KB di bundle → 148 KB offuscato);//   - il control flow flattening rallenta l'esecuzione;//   - gli stack trace degli errori in produzione diventano illeggibili,//     quindi il monitoraggio va ripensato;//   - debugProtection: true blocca DevTools con un ciclo di debugger, e//     rende l'esperienza ostile anche a chi sta solo ispezionando la//     propria pagina. È una scelta di postura, non di sicurezza.

Vale la pena essere espliciti sul rapporto costi/benefici, perché l'offuscamento viene di solito presentato come gratuito e non lo è.

Cosa compra. Tempo e attrito. Chi voleva copiare una funzione guardando il codice, con l'array di stringhe cifrato e il flusso di controllo appiattito, deve mettersi a lavorare sul serio. Per la stragrande maggioranza di chi guarda — che è curiosità, non spionaggio industriale — è sufficiente a far chiudere la scheda. È una difesa contro la copia opportunistica, e come tale funziona.

Cosa costa. Nel caso di questo sito, un bundle di 17 KB diventa 148 KB: quasi nove volte tanto, e sono byte che ogni visitatore scarica. Il control flow flattening ha un costo di esecuzione. E gli stack trace in produzione diventano illeggibili, il che significa che il monitoraggio degli errori va ripensato — o si accetta di non capire più perché qualcosa si rompe sui browser degli utenti.

Dove finisce la tecnica#

Se il valore da proteggere è la logica — un algoritmo, un modello di calcolo, una regola di business che vi distingue — la risposta non è un offuscatore più aggressivo: è non mandarla al browser. Il client raccoglie l'input, il server calcola, il client mostra il risultato. Quello che viaggia è il risultato, non il metodo.

E se il valore è il lavoro nel suo insieme — il design, la struttura, l'esperienza — la tutela è di natura diversa: il diritto d'autore protegge il codice sorgente come opera dell'ingegno indipendentemente da come è pubblicato, e una licenza esplicita nel repository e una nota nel footer valgono, in caso di copia palese, più di qualunque configurazione di build.