Codice sicuro

Codice pulito e codice sicuro sono la stessa cosa.

Una vulnerabilità è quasi sempre un bug normale finito nel posto sbagliato. Ecco perché la chiarezza conta, quali sono gli errori più comuni e cosa controllare prima di ogni rilascio.

Perché il codice pulito è più sicuro

Il codice che si capisce al primo colpo si rivede meglio, si testa meglio e nasconde meno sorprese. La sicurezza non è uno strato da aggiungere alla fine: nasce dalle stesse abitudini che rendono il codice leggibile.

Un esempio: la stessa query, scritta in due modi

Vulnerabile SQL injection

// l'input dell'utente diventa parte della query
const rows = await db.query(
  `SELECT * FROM users
   WHERE email = '${req.body.email}'`
);

Sicuro query parametrizzata

// query e dati viaggiano separati
const email = String(req.body.email ?? '').trim();
const rows = await db.query(
  'SELECT id, name FROM users WHERE email = $1',
  [email]
);

Se qualcuno scrive ' OR '1'='1 nel campo email, la prima versione restituisce tutti gli utenti del database. La seconda cerca semplicemente un’email strana e non trova niente. In più chiede solo le colonne che servono: se qualcosa va storto, esce meno.

Le 10 vulnerabilità più diffuse

L’OWASP Top 10 è la classifica di riferimento dei rischi per le applicazioni web, curata dalla comunità OWASP. L’edizione più recente è la 2025: la catena di fornitura del software sale al terzo posto ed entra una categoria nuova sulla gestione degli errori.

  1. A01

    Controllo degli accessi violatoBroken Access Control

    Un utente vede o modifica dati non suoi, per esempio cambiando /ordini/1042 in /ordini/1043 nell’indirizzo.

    Difesa: controlla i permessi sul server a ogni richiesta e nega tutto per default.

  2. A02

    Configurazione errataSecurity Misconfiguration

    Password di default, pannelli di amministrazione esposti, errori che mostrano lo stack trace, file pubblici per sbaglio.

    Difesa: configurazioni minime e ripetibili, niente modalità debug in produzione, header di sicurezza.

  3. A03

    Catena di fornitura del softwareSoftware Supply Chain Failures

    Dipendenze compromesse, pacchetti malevoli, pipeline di build manomesse. Nel 2025 è salita al terzo posto.

    Difesa: lockfile, 2FA, aggiornamenti controllati e meno dipendenze (ne parliamo sotto).

  4. A04

    Errori crittograficiCryptographic Failures

    Password salvate in chiaro o con hash veloci come MD5, dati sensibili che viaggiano senza HTTPS.

    Difesa: HTTPS ovunque, password con bcrypt, scrypt o Argon2, e mai crittografia fatta in casa.

  5. A05

    IniezioneInjection

    L’input dell’utente diventa codice: SQL injection, comandi di sistema, e anche le XSS che provi qui sotto.

    Difesa: query parametrizzate, textContent al posto di innerHTML, validazione sul server.

  6. A06

    Progettazione insicuraInsecure Design

    Il problema è nell’idea, non nel codice: un reset della password con una domanda facile da indovinare, un modulo senza limite di invii.

    Difesa: chiediti “come potrebbe essere abusato?” già mentre progetti la funzione.

  7. A07

    Autenticazione deboleAuthentication Failures

    Password deboli accettate, tentativi di login illimitati, sessioni che non scadono mai.

    Difesa: 2FA, limite ai tentativi, cookie di sessione HttpOnly e Secure.

  8. A08

    Integrità di software e datiSoftware or Data Integrity Failures

    Aggiornamenti, script o dati accettati senza verificarne l’origine, come uno script da CDN che può cambiare a tua insaputa.

    Difesa: firme e checksum, attributo integrity sugli script esterni.

  9. A09

    Log e allarmi insufficientiSecurity Logging and Alerting Failures

    Un attacco va avanti per settimane e nessuno se ne accorge, perché nessuno registra né guarda.

    Difesa: registra login falliti e azioni sensibili, con allarmi. E mai password nei log.

  10. A10

    Gestione errata delle eccezioniMishandling of Exceptional Conditions

    Novità del 2025: un errore imprevisto lascia il sistema in uno stato insicuro, per esempio un controllo dei permessi che, se fallisce, lascia passare.

    Difesa: in caso di errore nega l’accesso (“fail closed”), gestisci ogni eccezione, mostra messaggi generici.

Prova tu: come nasce un attacco XSS

Il Cross-Site Scripting succede quando un testo scritto da un utente viene trattato come HTML. Scrivi un commento qui sotto e guarda cosa farebbe il browser con innerHTML e con textContent.

Esempi:

el.innerHTML = commento

    el.textContent = commento

    Sicuro: il browser mostra il testo così com’è.

    Nessun rischio: la demo non esegue nulla. Analizza il testo dentro un <template>, un contenitore inerte in cui gli script non partono e le immagini non si caricano. Se ti serve davvero accettare HTML dagli utenti, passalo prima da un sanitizzatore come DOMPurify. Il browser sta imparando a farlo da solo con setHTML(), già in Chrome e Firefox ma non ancora in Safari.

    Il pericolo nelle dipendenze

    Un progetto JavaScript medio installa centinaia di pacchetti scritti da persone che non conosci. Dal 2025 questo rischio è diventato molto concreto.

    1. Il maintainer di chalk, debug e altri 17 pacchetti cade in un’email di phishing che imita il supporto npm. Per pochi minuti escono versioni che sostituiscono gli indirizzi dei wallet crypto, in pacchetti che insieme contano tra 2 e 3 miliardi di download a settimana. Socket

    2. Shai-Hulud, un worm che si replica da solo: ruba i token di npm e GitHub e le chiavi cloud, poi pubblica versioni infette di altri pacchetti. Oltre 500 pacchetti colpiti, con un’allerta della CISA americana. CISA

    3. Shai-Hulud 2.0 si attiva già durante l’installazione, nello script preinstall: 796 pacchetti con oltre 20 milioni di download settimanali. Bastava un npm install. Datadog Security Labs

    4. La risposta di npm: i vecchi token “classic” sono stati revocati e il login ora crea sessioni di due ore. Nel 2026 sono arrivate la pubblicazione con approvazione in 2FA (staged publishing) e la scansione antimalware al momento della pubblicazione. GitHub

    Come difenderti

    Segreti al sicuro, header al loro posto

    Le chiavi non vanno nel codice

    Password e chiavi API stanno nelle variabili d’ambiente, in un file .env escluso da Git. E ricorda: tutto ciò che finisce nel JavaScript del frontend è pubblico, anche se si chiama SECRET_KEY. Per questo Vite passa al browser solo le variabili che iniziano con VITE_.

    .gitignore

    .env
    .env.*
    !.env.example

    Se una chiave è finita in un commit, cancellarla non basta: la cronologia di Git la conserva. Va revocata e rigenerata subito.

    Header di sicurezza

    Poche righe nella configurazione del server chiudono intere famiglie di attacchi. Esempio nel formato _headers di Netlify e Cloudflare Pages:

    _headers

    /*
      Content-Security-Policy: default-src 'self'; object-src 'none'; frame-ancestors 'none'
      Strict-Transport-Security: max-age=31536000; includeSubDomains
      X-Content-Type-Options: nosniff
      Referrer-Policy: strict-origin-when-cross-origin
      Permissions-Policy: camera=(), microphone=(), geolocation=()
    • Content-Security-Policy: decide da dove possono arrivare script e risorse. È la seconda linea di difesa contro le XSS.
    • Strict-Transport-Security: il browser usa sempre HTTPS, anche se qualcuno scrive http://.
    • frame-ancestors 'none': nessun altro sito può incorporare il tuo in un iframe (clickjacking).

    Checklist prima del deploy

    Spunta man mano. I progressi restano salvati in questo browser.

    Completati: 0 di 12