Sicurezza

Segreti in vetrina: come una riga di codice può uccidere un progetto

Perché inserire credenziali nel codice è una bomba a orologeria — e come una gestione disciplinata delle variabili d’ambiente salva progetti, budget e nervi.

5 min di letturaGiugno 2026

Immagina una situazione classica: venerdì sera, la scadenza brucia. Uno sviluppatore prova in fretta un’integrazione, incolla una chiave API direttamente in un file di configurazione per «vedere solo se funziona», fa push e va al weekend sereno. Lunedì mattina la sorpresa: accessi compromessi, dati trafugati e una bolletta cloud simile a un numero di telefono.

Il motivo è banale — il segreto è rimasto nel codice ed è volato nella cronologia del repository.

In PolyCode Systems lavoriamo ogni giorno con infrastrutture complesse, integrazioni di marketplace e sistemi multi-agente. Il costo di un errore è troppo alto. Per questo la nostra prima e più rigida regola è: nessun segreto nel codice. Mai. Nemmeno «per cinque minuti» o «solo per test locali». Oggi parliamo del perché inserire dati riservati nel codice è sempre una bomba a orologeria.

Perché l’illusione di sicurezza costa così cara

Molti pensano «è un repo privato» o «commento quella riga prima del commit». In pratica questo approccio prima o poi fallisce. Ecco le tre trappole principali:

  1. Git non dimentica nulla. Se per sbaglio committi un file con la password del database o una chiave del gateway di pagamento, cancellarlo nel commit successivo non basta. Il segreto resta per sempre nella cronologia di Git. A un attaccante (o a un junior curioso) basta guardare la cronologia.
  2. L’effetto copia-incolla. Con la configurazione nel codice, spostare l’app da Dev a Stage o Prod diventa un campo minato. Bisogna cambiare le chiavi a mano a ogni deploy. Un refuso, e il server di test inizia ad addebitare denaro vero sull’account di produzione.
  3. Fughe via screenshot e log. Ciò che è nel codice riemerge nei log di errore, negli stack trace o a schermo durante il pair programming. Le variabili d’ambiente restano invisibili al codice — vivono a livello di server.

Lo standard aureo di PolyCode Systems

Abbiamo messo a punto un processo semplice ma senza compromessi. Non richiede costosi strumenti enterprise, ma disciplina ferrea.

  • Il file .env esiste, ma non è nel repo. Nella root c’è sempre un .env.example — un modello con tutte le variabili necessarie (es. DB_HOST=, STRIPE_API_KEY=) senza i valori. Lo sviluppatore lo copia, lo rinomina in .env e mette le sue chiavi. Il .env stesso è rigorosamente e permanentemente nel .gitignore.
  • Accesso isolato. Il codice non deve sapere dove gira. Chiede solo al sistema: «dammi il token del database». In locale lo fornisce un file locale; in produzione, le impostazioni d’ambiente via l’interfaccia dell’infrastruttura (es. Coolify o le variabili del server Linux).
  • Chiavi in CI/CD. Durante build e deploy automatici, i segreti passano solo dalle impostazioni sicure della pipeline (es. GitHub Secrets), che li iniettano direttamente nel build. Nessun file di configurazione spedito negli archivi.

Dalla teoria alla pratica

Guardiamo l’anti-pattern classico. I principianti spesso iniziano così:

L’anti-pattern

// ❌ How NOT to do it (a straight path to grey hair)
export const dbConfig = {
  host: "192.168.1.100",
  user: "admin",
  password: "SuperSecretPassword123" // Hello, public repository!
};

Sembra che passare alle variabili d’ambiente sia solo sostituire con process.env.PASSWORD. Ma andiamo oltre. Leggere process.env è metà dell’opera. L’altra metà è la validazione rigorosa all’avvio (fail fast). Se manca la chiave del database, l’app non deve avviarsi affatto. Meglio un crash rumoroso all’avvio che un boot silenzioso che restituisce 500 agli utenti reali. Ecco il pattern in TypeScript (con zod) diventato il nostro standard:

Lo standard aureo: validare l’ambiente

// ✅ Gold standard: validate the environment
import { z } from 'zod';

// 1. Declare the variables we truly need
const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_SECRET_KEY: z.string().min(10),
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
});

// 2. Validate whatever the system gave us
const env = envSchema.safeParse(process.env);

if (!env.success) {
  console.error('❌ Critical error: missing environment variables', env.error.format());
  process.exit(1); // Hard-stop the application
}

// 3. Export a typed config for the whole project
export const config = env.data;

Dov’è la magia?

La bellezza di questa architettura è che al codice non importa dove gira. Un .env locale in sviluppo e, al deploy, le stesse chiavi nel pannello del server. La logica di business resta invariata — nemmeno una riga da modificare.

Ottieni una certezza solidissima: se l’app è partita, ha tutti gli accessi necessari e sono validi.

Una checklist per il tuo team

Prima di chiudere l’articolo, controlla il tuo progetto su tre punti:

  1. Il .env è nel .gitignore proprio adesso?
  2. C’è un .env.example nella root, senza password reali, così un nuovo sviluppatore sa cosa configurare?
  3. La tua app va in crash all’avvio se cancelli o rinomini il file di configurazione di produzione?

Se è «sì» dappertutto — ottimo lavoro, il tuo codice è al sicuro. Se c’è un «no» — sai cosa fare nel prossimo sprint.

E nel tuo team?

Come passa il tuo team i segreti dagli sviluppatori al DevOps? Hai avuto epiche fughe di chiavi in produzione? Racconta la tua storia nei commenti.

Costruiamo prodotti su un’infrastruttura che prende sul serio i segreti.

Leggi l’articolo