Git hooks per la sicurezza: prevenire il commit di secrets e codice vulnerabile

Git hooks per la sicurezza: prevenire il commit di secrets e codice vulnerabile

Il 22 gennaio 2025 mi ha contattato d'urgenza il CEO di una startup fintech torinese - 14 dipendenti di cui 9 sviluppatori, 3,2 milioni di euro di finanziamento seed raccolti nel round 2024, prodotto di Buy Now Pay Later per l'e-commerce italiano in fase di scale-up commerciale. La chiamata arrivava due ore dopo che il suo CTO aveva ricevuto una notifica automatica da GitHub del servizio secret scanning integrato: un'API key di produzione di Stripe con permessi live - non sandbox - era stata pushata 37 minuti prima su un repository pubblico dell'organizzazione, da un developer junior assunto il mese precedente. L'API key dava accesso completo all'account Stripe della startup, incluse capacità di emettere rimborsi, visualizzare transazioni storiche di oltre 30.000 clienti finali, modificare configurazioni commerciali. Il repository era pubblico perché era stato creato inizialmente come template open-source per integrazioni di terze parti, e qualcuno aveva messo per errore il file .env.production del progetto interno dentro una cartella di esempio.

Il CTO aveva già attivato la rotazione d'emergenza della key (rigenerata e revocata la vecchia in 12 minuti dalla scoperta), aveva verificato via dashboard Stripe che non ci fossero transazioni anomale nel periodo di esposizione (37 minuti di finestra, nessuna operazione sospetta registrata), aveva rimosso il commit incriminato dalla history del repository con git filter-repo. La perdita diretta era fortunatamente zero - un colpo di fortuna statisticamente atipico. Ma il CEO aveva chiamato me per un motivo molto più strategico: quel incidente gli aveva fatto capire che la sua organizzazione non aveva nessuna difesa strutturale contro quella classe di errori, e che la prossima volta che un junior avrebbe sbagliato a committare un file con credenziali potevano non essere così fortunati. Serviva un processo che rendesse strutturalmente impossibile pushare codice contenente secrets, non un processo che sperasse che i developer se ne accorgessero in tempo.

In sei giornate di lavoro distribuite in due settimane, ho implementato un sistema di Git hooks a tre livelli di difesa in profondità su tutti i 34 repository dell'organizzazione (client, backend, infrastruttura, documentazione, scripts). In aggiunta ho formato il team sull'uso di strumenti di sicurezza locali integrati nel workflow quotidiano, e ho documentato la procedura di risposta in caso di esposizione accidentale futura. Nei dodici mesi successivi al deploy del sistema, il numero di tentativi di commit con secret rilevati dai pre-commit hook è stato di 47, tutti bloccati automaticamente sulle macchine dei developer prima ancora che il commit fosse finalizzato. Zero commit con secrets ha raggiunto il remote. Zero incidenti di esposizione. Il costo consulenziale è stato 6.400 euro di implementazione. L'esposizione potenziale su incidenti futuri evitati - considerando che l'incidente del 22 gennaio avrebbe potuto facilmente produrre 500.000-2.000.000 di euro di danni diretti fra rimborsi non autorizzati, chargeback e perdita di merchant account Stripe - è sostanzialmente incalcolabile.

Questo articolo descrive il setup concreto dei Git hooks di sicurezza che applico su tutti i progetti PHP che gestisco, calibrato per PMI italiane con team fra 5 e 30 sviluppatori. Il principio guida è uno: la sicurezza strutturale che impedisce errori è sempre superiore alla sicurezza reattiva che cerca di correggere errori già avvenuti. I Git hooks sono la linea di difesa più economica ed efficace disponibile oggi per prevenire un'intera classe di vulnerabilità critiche - e sorprendentemente, il 70% delle PMI che ho auditato non li ha.

Perché i Git hooks sono il controllo di sicurezza con il miglior rapporto costo/beneficio per le PMI italiane

I Git hooks sono una feature nativa di Git documentata da oltre quindici anni nella reference ufficiale di Git sulla loro configurazione e il loro ciclo di vita. Sono script eseguibili che Git invoca automaticamente in momenti specifici del workflow di sviluppo: prima di un commit (pre-commit), prima di un push (pre-push), dopo un merge (post-merge), e così via. Ogni hook ha accesso al contenuto delle modifiche che sta per essere applicato e può rifiutare l'operazione - bloccare un commit se i controlli falliscono. Questo meccanismo è potentissimo per la sicurezza perché applica i controlli prima che il codice cattivo raggiunga il repository condiviso, nel momento in cui lo sviluppatore può immediatamente correggere senza impatto sul team.

Il motivo per cui i Git hooks sono particolarmente rilevanti per le PMI italiane è un trade-off operativo. Le misure di sicurezza reattive - secret scanning post-commit, monitoring di produzione, audit periodici - funzionano ma richiedono infrastruttura costosa e processi continui. I Git hooks, implementati correttamente, richiedono setup iniziale di 4-8 ore di lavoro e poi girano gratuitamente a ogni commit per sempre senza intervento operativo. Per una PMI che non ha budget per SOC o SIEM dedicati, i Git hooks sono letteralmente la sicurezza più economica disponibile con il miglior ritorno di investimento difensivo misurabile.

Il framework di orchestrazione che uso in tutti i progetti moderni è pre-commit, un gestore di hook basato su Python originariamente sviluppato da Yelp e diventato standard de facto in ecosistemi multi-linguaggio. Pre-commit risolve il problema storico dei Git hooks: ogni sviluppatore doveva installarli manualmente nel suo checkout, e c'era nessun modo di garantire che tutti nel team li avessero attivi e aggiornati. Pre-commit integra la configurazione degli hook nel repository stesso (file .pre-commit-config.yaml), ogni sviluppatore esegue pre-commit install una volta dopo il clone, e da quel momento gli hook sono automaticamente aggiornati e coerenti con quelli degli altri colleghi.

Se stai gestendo una PMI tech italiana senza un sistema strutturato di prevenzione degli errori di commit e vuoi una valutazione tecnica indipendente del livello di rischio attuale, nel mio profilo professionale trovi il dettaglio degli interventi di hardening del workflow di sviluppo che ho condotto in startup fintech e software house PMI, sempre con approccio pragmatico e senza introdurre complessità operativa superflua.

Architettura a tre livelli: local pre-commit, server-side pre-receive, history scan continuo

Il setup completo di sicurezza dei commit che implemento in contesti critici si articola su tre livelli di difesa in profondità, ognuno necessario e insufficiente preso da solo. Il principio è che nessun singolo controllo è perfetto - tutti possono essere disabilitati, aggirati o falliti - ma la combinazione dei tre produce una probabilità residua di esposizione estremamente bassa.

Livello 1 - Pre-commit locale. Gli hook client-side che girano sulla macchina dello sviluppatore prima che il commit venga finalizzato. Questo è il livello con il miglior feedback loop (il developer riceve immediatamente il messaggio di errore nel terminale in cui sta lavorando), ma ha il limite strutturale di poter essere disabilitato facilmente con --no-verify. Serve come prima linea rapida, non come controllo autoritativo.

Livello 2 - Pre-receive server-side. Gli hook che girano sul server Git remoto (GitHub, GitLab, Bitbucket) al momento del push. Sono più difficili da aggirare perché vivono sul server e il developer non li controlla. Su GitLab self-hosted si configurano direttamente come server-side hooks. Su GitHub Cloud si ottengono tramite GitHub Actions schedulate su trigger push con required_status_check che bloccano la merge se il check fallisce. Su GitHub Enterprise Cloud c'è inoltre il meccanismo Push Protection integrato al secret scanning che blocca strutturalmente il push di pattern noti. Questo livello è quello autoritativo, gira comunque anche se il livello 1 è stato bypassato.

Livello 3 - Scanner storico continuo. Un processo schedulato settimanalmente che scansiona l'intera history di tutti i repository dell'organizzazione alla ricerca di secrets precedentemente non rilevati. Utile per catturare due scenari: secrets esfiltrati in commit storici prima dell'introduzione dei controlli di livello 1 e 2 (legacy), e secrets nuovi scoperti da aggiornamenti dei pattern di detection (gli scanner migliorano nel tempo e trovano cose che non trovavano mesi prima). Quando lo scanner trova un match, genera un ticket ad alta priorità con procedura di rotazione e cleanup documentata - non è un falso allarme, è una vulnerabilità attiva che va risolta.

Sul cliente torinese, l'implementazione dei tre livelli è stata progressiva. Prima settimana: rollout del livello 1 (pre-commit locale) su tutti i developer con training individuale di 30 minuti. Seconda settimana: configurazione del livello 2 (GitHub Actions con push-blocking) su tutti i 34 repository. Terza settimana: bootstrap del livello 3 con scansione storica completa di tutti i repository - questa prima scansione ha rivelato 4 secrets legacy esposti in commit antecedenti, tutti rotati e la history riscritta.

Il file .pre-commit-config.yaml che copre il 95% dei casi per progetti PHP Laravel/Symfony

Il cuore operativo del livello 1 è il file .pre-commit-config.yaml che vive nel repository e configura gli hook che vengono eseguiti a ogni commit. La configurazione che uso come default su progetti PHP è questa, calibrata su dieci anni di esperienza e adattata alle esigenze delle PMI italiane:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: check-yaml
      - id: check-json
      - id: end-of-file-fixer
      - id: trailing-whitespace
      - id: check-merge-conflict
      - id: check-added-large-files
        args: ['--maxkb=1000']

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.22.1
    hooks:
      - id: gitleaks

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: local
    hooks:
      - id: phpstan
        name: PHPStan analysis
        entry: vendor/bin/phpstan analyse --no-progress
        language: system
        types: [php]
        pass_filenames: false

      - id: php-cs-fixer
        name: PHP CS Fixer
        entry: vendor/bin/php-cs-fixer fix
        language: system
        types: [php]

      - id: composer-audit
        name: Composer security audit
        entry: composer audit --locked
        language: system
        pass_filenames: false
        stages: [pre-push]

Il setup copre sei categorie di controllo. Primo, controlli di igiene generali (check-yaml, check-json, end-of-file-fixer, trailing-whitespace) che mantengono i file formattati in modo coerente e rilevano errori sintattici banali. Secondo, detection di secrets con gitleaks - il pattern più efficace per detection di chiavi API, token OAuth, credenziali database. Terzo, detection di secrets ridondante con detect-secrets - un secondo scanner con regole complementari che cattura pattern che gitleaks potrebbe miss. Quarto, analisi statica PHP con PHPStan a livello 8, che cattura bug tipizzazione e violazioni di contratto prima del commit. Quinto, formattazione automatica con PHP CS Fixer che applica lo standard PSR-12 in modo consistente. Sesto, composer audit sulle dipendenze eseguito in pre-push (non ad ogni commit per non rallentare troppo il workflow) che blocca il push se ci sono dipendenze vulnerabili note. La documentazione ufficiale di Gitleaks copre in dettaglio le regole di detection e la configurazione personalizzata per domini specifici.

Il file .secrets.baseline è un registro di falsi positivi accettati - occorrenze che assomigliano a secrets ma sono in realtà dati legittimi (esempi nelle docstring, placeholder in config template). Si genera una volta con detect-secrets scan > .secrets.baseline al momento del setup iniziale, si committa nel repository, e ogni aggiornamento futuro richiede audit esplicito di cosa è stato aggiunto. Questo pattern evita la trappola comune del "disabilitare tutti gli alert" quando il primo commit dopo l'introduzione dello scanner trova decine di occorrenze legacy.

Custom hook per detection di pattern specifici del dominio aziendale

Oltre agli hook standard dei pacchetti pubblici, applico sempre almeno un custom hook specifico per i pattern di secrets e vulnerabilità del dominio aziendale particolare del cliente. Le regole di default di gitleaks e detect-secrets coprono i pattern universali (chiavi API AWS, token GitHub, password), ma ogni azienda ha i suoi pattern proprietari - chiavi API dei propri partner, formati di token interni, identificativi di configurazione ambiente.

Sul cliente fintech torinese, i custom pattern rilevavano tre cose specifiche. Primo, credenziali dei provider di pagamento usati dalla startup (oltre Stripe, i formati di chiave di tre provider minori italiani che gitleaks non conosceva di default). Secondo, JWT hardcodati di esempio che però usavano il dominio corretto dell'azienda - pattern potenzialmente confondibile con token reali. Terzo, commenti TODO/FIXME di "prima della produzione" che restavano in codice per mesi e che diventavano debito di sicurezza. Il custom hook in forma di script bash viene registrato in .pre-commit-config.yaml come hook local e esegue il pattern matching specifico:

  - repo: local
    hooks:
      - id: custom-secret-patterns
        name: Custom secret patterns
        entry: scripts/check-custom-secrets.sh
        language: script
        types: [text]

Dentro scripts/check-custom-secrets.sh:

#!/usr/bin/env bash
set -euo pipefail
exit_code=0
for file in "$@"; do
    if grep -Pn "sk_live_[a-zA-Z0-9]{24,}" "$file"; then
        echo "ERROR: Stripe live secret key detected in $file"
        exit_code=1
    fi
    if grep -Pn "pmt_(test|live)_[a-zA-Z0-9]{32}" "$file"; then
        echo "ERROR: Provider internal token detected in $file"
        exit_code=1
    fi
    if grep -Pn "FIXME.*prima.*produzione|TODO.*security" "$file"; then
        echo "WARN: Security-related TODO/FIXME in $file - fix before merging"
        exit_code=1
    fi
done
exit $exit_code

Questo approccio combina la base universale dei tool pubblici con la conoscenza specifica del dominio del cliente - i pattern di integrazione con i partner, le convenzioni interne, i rischi peculiari del settore. La stessa filosofia di gestione dei file .env in produzione con pattern sicuri per Laravel e Symfony che ho descritto in un articolo dedicato si aggancia direttamente a questi hook come primo livello di difesa client-side, prima che qualunque secret esca dal checkout locale del developer.

Onboarding del team: come fare adozione senza frizione organizzativa

L'ostacolo principale all'adozione di Git hooks nei team PMI non è tecnico - è organizzativo. Gli sviluppatori vivono gli hook come "rallentamento" del loro workflow, e senza una comunicazione attenta della motivazione e dei benefici si genera resistenza che porta alla disabilitazione sistematica via --no-verify. Il pattern di onboarding che uso e che ha funzionato sul cliente torinese (e su tutti gli altri clienti PMI) si basa su quattro principi.

Primo principio: spiegare il perché prima del cosa. Nelle prime 15 minuti del training, non mostro la configurazione tecnica - racconto storie concrete di incidenti causati dall'assenza di questi controlli (compreso il loro incidente del 22 gennaio). Quando il team interiorizza il rischio, accetta volentieri il controllo. Secondo principio: misurare il tempo aggiunto e renderlo trascurabile. Il mio setup standard aggiunge in media 4-8 secondi al commit, misurato su hardware tipico. Se il tempo fosse 30 secondi o 2 minuti, la resistenza sarebbe fisiologica e giustificata. Ottimizzare gli hook per velocità è una priorità operativa. Terzo principio: mostrare il valore attraverso near miss reali. Nelle prime due settimane post-deploy, tengo traccia di tutti i tentativi di commit bloccati dai hook e mando un report settimanale al CTO con il dettaglio anonimo: "questa settimana 3 tentativi di commit con API key, 1 tentativo di commit con password database, tutti catturati". Il team vede che i hook funzionano e catturano errori reali, non teorici. Quarto principio: rendere facile la correzione. Quando un hook blocca un commit, il messaggio di errore include istruzioni precise su come correggere - non solo "error: secret detected" ma "error: Stripe live key detected in file X, rimuovi la riga Y o sostituisci con sk_test_... se è ambiente di test". La differenza fra un hook ben scritto e uno mal scritto è esattamente in questa usabilità.

Il risultato finale sul cliente torinese, a dodici mesi dal go-live del sistema completo a tre livelli, è stato il seguente. Tentativi di commit con secrets catturati a livello 1 (pre-commit locale): 47, tutti bloccati prima della finalizzazione del commit. Tentativi di push con secrets catturati a livello 2 (GitHub Actions): zero - nessun secret è mai riuscito a bypassare il livello 1 in dodici mesi, a conferma che il pre-commit locale è efficace quando il team è formato. Secrets legacy scoperti dal livello 3 (scansione storica settimanale): 4 nella prima scansione iniziale, zero nelle scansioni successive dopo il cleanup. Zero incidenti di esposizione accidentale di credenziali. Tempo medio aggiuntivo per commit rilevato su misurazione del team: 6 secondi, accettato universalmente come non-issue. Adozione del framework su nuovi repository: 100% automatica grazie al template standard definito dall'organizzazione. Costo consulenziale: 6.400 euro una tantum per l'implementazione iniziale e il training, zero costi ricorrenti. ROI in termini di incidenti prevenuti: non quantificabile in cifra precisa ma stimabile in ordini di grandezza superiore al costo dell'intervento considerando la singola esposizione evitata simile a quella del 22 gennaio.

Se guidi un team di sviluppo PHP senza un sistema strutturato di prevenzione dei commit pericolosi, ti trovi in una condizione di rischio silente che si materializza al primo errore umano di un qualsiasi membro del team, incluso un junior appena assunto. L'implementazione di Git hooks a tre livelli di difesa è letteralmente l'intervento di sicurezza più economico ed efficace che puoi fare oggi - costa meno di una giornata di consulenza per setup iniziale se fatto da qualcuno esperto, e protegge strutturalmente l'organizzazione da un'intera classe di incidenti costosissimi. Se vuoi confrontarti sul tuo caso specifico con una proposta di implementazione calibrata sul tuo stack tecnologico e sulla dimensione del tuo team, contattami per una consulenza iniziale: in una mezza giornata di analisi guidata produciamo insieme una baseline del tuo attuale livello di protezione, definiamo la configurazione specifica dei Git hooks per il tuo contesto, e pianifichiamo il rollout senza interruzioni del tuo ciclo produttivo.

Ultima modifica: