Code propre, zéro stress : automatiser la qualité et la sécurité avant le push

Git hooks pre-commit et pre-push pour automatiser la qualité et la sécurité du code. Mise en place pratique avec Husky, Lint-staged et Gitleaks.

Illustration : Automatiser la qualité et la sécurité du code avec les Git hooks

On a tous connu ce moment de solitude. Ce petit frisson désagréable quand on réalise, trois minutes après un déploiement, qu'on a laissé un var_dump ou un console.log un peu gênant en plein milieu du code. Ou pire, cette sueur froide quand on s'aperçoit qu'une clé d'API a failli partir sur le dépôt public.

Même les développeurs les plus seniors font des erreurs d'inattention. La fatigue, les délais serrés ou simplement une distraction, et c'est la qualité du code qui en pâtit.

Le problème, c'est que compter uniquement sur sa propre vigilance (ou sur celle des collègues lors de la Code Review) ne suffit plus. Pour éviter d'accumuler de la dette technique silencieuse, il faut arrêter de "chercher" les erreurs et commencer à empêcher le système de les accepter.

C'est là que les Git hooks entrent en jeu.

Git Hooks : le portique de sécurité de votre dépôt

Si on devait résumer le concept : un Git Hook, c'est un script qui se déclenche automatiquement lorsqu'un événement précis survient dans votre dépôt Git.

Imaginez un petit robot caché dans votre dossier .git/hooks. La plupart du temps, il dort. Mais dès que vous tapez git commit ou git push, il se réveille, exécute une série d'ordres, et décide si oui ou non, vous avez le droit de continuer.

Pour bien comprendre l'intérêt, prenons une analogie simple : le portique de sécurité à l'aéroport.

Quand vous préparez votre valise (vous stagez vos fichiers), tout va bien. Mais pour monter dans l'avion (le dépôt distant), vous devez passer la sécurité.

  • Si vous avez une bouteille d'eau interdite (une erreur de syntaxe ou un conflit de merge), le portique sonne. Vous êtes bloqué. Vous ne pouvez pas passer tant que vous n'avez pas jeté la bouteille.
  • Si tout est en règle, le portique reste silencieux et vous avancez.

L'idée n'est pas de vous punir, mais d'empêcher un problème local de devenir un problème collectif. On va se concentrer sur les deux hooks les plus utiles :

Le pre-commit : le gardien de la porte d'entrée

C'est le hook le plus courant et le plus utile au quotidien. Il se lance après que vous ayez tapé git commit, mais avant que le commit ne soit réellement créé dans l'historique.

  • Son rôle : L'analyse rapide. Il vérifie le formatage, cherche les erreurs de syntaxe grossières ou les clés oubliées.
  • L'avantage : Le feedback est immédiat. Si ça plante, c'est uniquement sur votre machine. Personne ne saura jamais que vous avez oublié un point-virgule.
  • La règle d'or : Il doit être rapide. Si votre pre-commit met 2 minutes à s'exécuter, vous allez finir par le désactiver (on se connaît).

Le pre-push : la dernière ligne de défense

Celui-ci intervient juste avant que votre code ne soit envoyé vers le serveur (GitHub, GitLab, Bitbucket). C'est l'étape ultime avant que le code ne devienne "public" pour le reste de l'équipe.

  • Son rôle : La validation de sécurité et de stabilité. C'est ici qu'on peut lancer des tests unitaires un peu plus lourds ou vérifier qu'on ne casse pas la build.
  • Pourquoi l'utiliser : C'est votre ceinture de sécurité. Si, par miracle, une horreur est passée à travers le pre-commit, le pre-push est là pour l'intercepter avant qu'elle ne pollue la branche main.

En résumé : configurer des hooks pre-commit et pre-push, c'est s'assurer que le code qui arrive sur le dépôt respecte vos standards. Par définition.

Formatage, linting, sécurité : trois piliers, trois types d'outils

Maintenant que le portique est en place, qu'est-ce qu'on y filtre ? Je découpe toujours mes vérifications en trois piliers distincts.

L'erreur classique est de tout mélanger. Pourtant, chaque pilier a son propre rôle et ses propres outils.

Le formatage : la fin des débats stériles

Soyons honnêtes : combien d'heures avez-vous perdues en Code Review à débattre des points-virgules, des virgules qui traînent (trailing commas) ou de la guerre sainte "Espaces vs Tabulations" ?

Trop. La réponse est : trop.

Le rôle du formatage automatique (via un hook pre-commit) est de standardiser le code avant même qu'il ne soit partagé. Le développeur écrit comme il veut, le hook repasse derrière et formate selon les règles de l'équipe.

  • L'objectif : Rendre la base de code uniforme, peu importe qui a écrit la fonction.
  • Les outils : Prettier (le standard du web), Black (Python), Rustfmt ou GoFmt.
  • Le gain : On arrête de parler de forme pour se concentrer sur le fond.

Le linting : chasser la dette technique

Si le formatage s'occupe de l'esthétique, le linting s'occupe de la grammaire et de la logique. C'est ici qu'on utilise des outils d'analyse statique.

Le linter va scanner vos fichiers pour repérer :

  • Les variables déclarées mais jamais utilisées (du bruit inutile).
  • Les erreurs de logique potentielles (ex: une condition qui est toujours vraie).
  • La complexité cyclomatique (ces fonctions de 200 lignes avec 15 if/else imbriqués).

C'est votre première barrière contre la dette technique. En bloquant ces "mauvaises odeurs" (code smells) dès le pre-commit, vous gardez une codebase saine.

  • Les outils : ESLint (JS/TS), Pylint / Ruff (Python), SonarLint (multi-langages).

La sécurité : le point critique

Vous pouvez avoir le code le plus propre du monde, si vous committez une clé d'API AWS ou un mot de passe de base de données en clair, le projet est en danger.

Et ne pensez pas que "supprimer le fichier après coup" suffit. Git a une mémoire d'éléphant : une fois commité, le secret reste dans l'historique .git, accessible à quiconque clone le projet.

Il faut empêcher le commit de secrets à la source. Les hooks scannent chaque ligne ajoutée à la recherche de patterns ressemblant à des clés privées, des tokens ou des certificats.

  • L'objectif : Éviter la fuite de données.
  • Les outils :
    • Gitleaks ou TruffleHog : pour détecter les secrets.
    • Audit de dépendances (via npm audit ou pip-audit) : pour vérifier que vous n'installez pas une librairie vérolée (souvent placé en pre-push car un peu plus lent).

Mise en pratique : deux approches selon votre stack

La théorie, c'est bien, mais comment on installe ça sans y passer trois jours ? Voici les deux configurations que je mets en place le plus souvent.

Option A : l'approche universelle avec le framework pre-commit

C'est mon couteau suisse préféré. Bien qu'écrit en Python, cet outil est agnostique : il peut gérer du JS, du PHP, du Terraform, du Bash, etc. C'est la solution idéale si votre dépôt contient plusieurs langages.

Comment ça marche ? On crée un simple fichier .pre-commit-config.yaml à la racine. On y liste les "repos" qu'on veut utiliser. L'outil gère l'installation des scripts tout seul.

Exemple de configuration standard :

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace  # Supprime les espaces en fin de ligne
      - id: end-of-file-fixer    # Assure une ligne vide à la fin
      - id: check-yaml           # Vérifie la syntaxe des fichiers YAML

  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black                # Formate le code Python automatiquement

Une fois ce fichier créé, un simple pre-commit install suffit. Désormais, à chaque commit, ces outils se lanceront. Propre, centralisé et facile à maintenir.

Option B : la voie Node.js (Husky + Lint-staged)

Si vous êtes dans un environnement 100% JavaScript ou TypeScript, inutile d'aller chercher un outil Python. On reste dans la famille avec npm.

Ici, on utilise un duo :

  1. Husky : Il permet d'activer les Git hooks facilement via le package.json.
  2. Lint-staged : Au lieu de scanner tout le projet, il ne scanne que les fichiers qui sont dans la staging area (ceux que vous êtes en train de commiter). Résultat : le workflow reste rapide.

Exemple de mise en place dans le package.json :

{
  "scripts": {
    "prepare": "husky install"
  },
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "**/*.{json,css,md}": [
      "prettier --write"
    ]
  }
}

Avec cette configuration, dès que vous faites un git commit, Husky déclenche Lint-staged. Ce dernier regarde vos fichiers modifiés, leur applique un coup de propre (ESLint + Prettier), les rajoute au commit et... c'est tout.

Sécurité avancée : scanner les dépendances avant le push

Si le linting et le formatage sont la toilette du code, cette étape en est le système immunitaire.

Soyons réalistes : dans un projet web moderne, le code que vous écrivez manuellement ne représente souvent que 10 à 20% du volume total. Le reste ? Vos dépendances (le fameux node_modules ou vendor).

C'est là que se cachent les Supply Chain Attacks. Vous pouvez coder de manière ultra-sécurisée, si vous importez une librairie qui contient une faille critique (CVE), vous ouvrez la porte.

Pourquoi le pre-push ?

Scanner toutes les dépendances peut prendre quelques secondes, voire une minute. Trop lent pour un pre-commit quotidien. Le pre-push est le bon endroit : c'est le dernier rempart avant que le code ne quitte votre machine.

Les outils

  1. L'approche native : npm audit (ou yarn audit) Si vous êtes sur Node.js, c'est intégré. Vous pouvez configurer un hook pre-push qui empêche l'envoi du code si une faille haute ou critique est détectée.

    • La commande : npm audit --audit-level=high
  2. L'approche robuste : Snyk ou Trivy Pour aller plus loin, des outils comme Snyk scannent non seulement les dépendances, mais aussi vos images Docker ou vos fichiers de configuration (Infrastructure as Code).

    • L'avantage : Ils proposent souvent la correction automatique pour combler la faille.

Et en termes business, c'est simple :

Corriger une faille pendant le développement coûte quelques minutes. La corriger en production peut coûter des milliers d'euros en audit, en temps d'arrêt et en perte de confiance.

Les bonnes pratiques : ne pas se faire détester par son équipe

Mettre en place des verrous, c'est bien. Mais si votre workflow prend 5 minutes à chaque commit, vos collègues (ou vous-même dans deux semaines) finiront par tout désactiver.

L'automatisation doit être une aide, pas un obstacle. Mes règles d'or :

  • Règle #1 : la vitesse avant tout. Un hook pre-commit doit être quasi-instantané. C'est pour ça que lint-staged est important : on ne scanne que ce qui a bougé. Plus de 10-15 secondes, c'est trop long.
  • Règle #2 : local vs CI/CD, chacun son rôle. Ne tentez pas de tout faire en local. Les tests unitaires lourds, les tests E2E ou les builds complexes n'ont rien à faire dans un hook pre-push. Laissez ces tâches à votre pipeline CI (GitHub Actions, GitLab CI). Votre machine sert à coder, pas à compiler l'univers.
  • Règle #3 : l'issue de secours. Parfois, on a besoin de commiter du code "sale" temporairement (pour sauvegarder son travail avant de partir en week-end ou changer de branche en urgence). Git a prévu le coup avec l'option --no-verify (ou -n).
    • Exemple : git commit -m "wip: save work" --no-verify L'outil est là pour vous servir, pas pour vous bloquer.

Bref

Configurer tout ça prend quelques heures au début d'un projet. Après, c'est transparent.

Moins de charge mentale : vous ne vous demandez plus "est-ce que j'ai oublié de formater ?" ou "est-ce qu'un secret traîne dans le commit ?". Des Code Reviews qui parlent d'architecture au lieu de débattre des points-virgules. Et une sécurité qui ne dépend plus de la vigilance humaine un vendredi à 18h.

Si votre workflow de commit ressemble encore au Far West, faites-moi signe, c'est le genre de mise en place qui se fait en quelques heures.