Due diligence technique : comment je mène les huit jours
On ne voit pas une dette technique dans une démo. On la voit dans l’historique git, les dépendances, la CI et le dernier test de restauration. Voici ma méthode, commande par commande, jusqu’aux clauses du protocole.

Dans une levée, une cession ou un rachat, la due diligence financière et juridique suit une méthode rodée. La partie technique, elle, se résume trop souvent à une démonstration du produit et à une liste de technologies. On ne voit pas une dette technique dans une démo. On la voit dans l’historique git, dans le fichier de verrouillage des dépendances, dans la configuration de la CI et dans la date du dernier test de restauration.
Voici comment je mène concrètement une due diligence technique sur huit jours : ce que je demande, les commandes que je lance, ce qui trompe dans leurs résultats, et comment je transforme les constats en jours de travail puis en clauses. Les sorties montrées sont des exemples génériques, pas celles d’un client.
1. Le cadre : huit jours, en lecture seule
Je demande trois choses avant de commencer, et rien de plus :
- un accès en lecture aux dépôts (rôle « Reporter » sur GitLab, « Read » sur GitHub), y compris aux dépôts archivés et à la configuration de la CI ;
- un accès en lecture seule à l’hébergement (par exemple un rôle
ReadOnlyAccesssur AWS, ou « Lecteur » sur GCP ou Azure), jamais un accès en écriture, jamais un accès direct à la base de production ; - quatre à six entretiens : le dirigeant, le responsable technique, et surtout les développeurs qui écrivent réellement le code.
La lecture seule n’est pas une précaution de forme. Elle protège la cible (je ne peux rien casser), elle me protège (je ne peux pas être tenu pour responsable d’un incident pendant l’audit), et elle force l’équipe à me montrer elle-même ce que je ne peux pas lancer, comme une restauration de sauvegarde. Ce qu’elle refuse de montrer est déjà un constat.
Je travaille sur un clone miroir, pour avoir toutes les branches et tous les tags, pas seulement la branche principale :
git clone --mirror git@gitlab.example.com:cible/plateforme.git
git clone plateforme.git plateforme && cd plateforme
git branch -r | wc -l # nombre de branches distantes
git tag --sort=-creatordate | head -5 # dernières versions publiées
Le déroulé type sur huit jours :
| Jour | Ce que je fais |
|---|---|
| 1 | Entretien de cadrage, ouverture des accès, inventaire des dépôts, des environnements et des comptes |
| 2 | Historique git : qui écrit le code, à quel rythme, où se concentrent les changements |
| 3 | Volume et structure du code, versions des langages et des frameworks |
| 4 | Dépendances : vulnérabilités, retard de versions, licences, secrets dans l’historique |
| 5 | Analyse statique, tests, couverture, lecture de la CI et du déploiement |
| 6 | Infrastructure, sauvegardes et test de restauration, supervision |
| 7 | Entretiens avec les développeurs, vérification des constats, chiffrage en jours |
| 8 | Rédaction du rapport et restitution |
Le périmètre peut descendre à cinq jours pour un seul dépôt, ou monter à dix pour plusieurs produits. Mais l’ordre ne change pas : les mesures d’abord, les entretiens ensuite. Si je fais les entretiens en premier, j’audite le discours de l’équipe au lieu d’auditer le code.
2. L’historique git : qui sait, et à quel rythme
La première question d’une due diligence n’est pas « le code est‑il bon ? » mais « qui le comprend ? ». L’historique git y répond mieux que n’importe quel organigramme.
git shortlog -sn --no-merges --since="12 months ago"
Une sortie type :
1412 Développeur A
187 Développeur B
64 Prestataire C
12 dependabot[bot]
Ici, une personne porte plus de 80 % des commits de l’année. C’est le bus factor : le nombre de personnes qu’il faut perdre pour que plus personne ne sache faire évoluer le produit. À un, le risque est critique, quelle que soit la qualité du code.
Trois pièges avant de conclure :
- Les identités multiples. Le même développeur commite avec son adresse personnelle et son adresse professionnelle. Je vérifie avec
git shortlog -sneet je regroupe les alias dans un fichier.mailmaplocal avant de compter. - Les squash merges. Si l’équipe fusionne ses branches en un seul commit, l’auteur affiché est parfois celui qui a cliqué sur « Merge », pas celui qui a écrit le code. Je recoupe avec les demandes de fusion dans GitLab ou GitHub.
- Les commits ne sont pas du savoir. Quelqu’un qui fait beaucoup de petits commits n’est pas forcément celui qui connaît la facturation. Je regarde donc qui touche les fichiers critiques :
git log --since="24 months ago" --format='%an' -- src/Billing/ | sort | uniq -c | sort -rn
Ensuite, le rythme. Le nombre de commits par mois sur deux ans montre si le produit vit, ralentit ou a été abandonné puis relancé :
git log --since="24 months ago" --no-merges --format='%ad' --date=format:'%Y-%m' \
| sort | uniq -c
Une courbe qui s’effondre six mois avant la cession, c’est souvent un développeur parti, ou une équipe occupée à préparer la vente plutôt qu’à faire évoluer le produit. Une courbe régulière avec des pics avant chaque version est plutôt bon signe.
Enfin, les fichiers qui changent le plus :
git log --since="12 months ago" --no-merges --name-only --format='' \
| grep -v '^$' | sort | uniq -c | sort -rn | head -15
Un même contrôleur de 3 000 lignes modifié deux cents fois dans l’année, c’est le point chaud : là où chaque évolution coûte le plus cher, et là où les régressions naissent. C’est la meilleure mesure que je connaisse de la question « combien coûte une évolution aujourd’hui ? ».
3. Le volume et la forme du code
Je mesure le volume avec cloc ou tokei, en excluant le code des dépendances :
cloc . --exclude-dir=vendor,node_modules,var,public/build
# ou
tokei . --exclude vendor --exclude node_modules
Language files blank comment code
PHP 1840 31200 18400 142000
JavaScript 310 4100 1900 28500
Twig 420 2100 120 14800
YAML 95 300 80 4200
Le chiffre seul dit peu. Ce qui compte, c’est ce qu’on trouve en le regardant de près. Un dossier lib/ qui contient une bibliothèque copiée à la main au lieu d’être installée par Composer : elle ne sera jamais mise à jour, et composer audit ne la verra pas. Du JavaScript minifié commité dans le dépôt : cloc le compte comme du code écrit par l’équipe. Je vérifie toujours les plus gros fichiers :
find src -name '*.php' -exec wc -l {} + | sort -rn | head -10
Puis les versions. Je relève la version du langage et du framework dans composer.json, package.json, les Dockerfile et la configuration des serveurs, car elles ne sont pas toujours les mêmes. Je les compare ensuite aux dates de fin de support publiées par endoflife.date :
curl -s https://endoflife.date/api/php.json \
| jq -r '.[:6][] | "\(.cycle)\tfin de support : \(.eol)"'
Une version en fin de vie n’est pas une opinion, c’est une date. Après elle, plus aucune faille n’est corrigée par l’éditeur. La migration devient obligatoire, et c’est souvent le plus gros poste du chiffrage, parce qu’elle entraîne celle du framework et d’une partie des bibliothèques.
4. Les dépendances : vulnérabilités, retard et licences
Je lance les outils standard de chaque écosystème, sur le fichier de verrouillage, qui décrit ce qui tourne réellement :
composer audit --locked
npm audit --omit=dev
osv-scanner scan source -r .
Une sortie type de composer audit :
Found 7 security vulnerability advisories affecting 4 packages:
+-------------------+----------------------------------------------+
| Package | symfony/http-kernel |
| Severity | high |
| CVE | CVE-XXXX-XXXXX |
| Affected versions | >=5.0.0,<5.4.20 |
+-------------------+----------------------------------------------+
Ce qui trompe ici, c’est le nombre. Sept alertes peuvent valoir une demi-journée si ce sont des mises à jour mineures, ou plusieurs semaines si la correction n’existe que dans une version majeure que le reste du code ne supporte pas. Je croise donc avec le retard de versions :
composer outdated --direct --major-only
doctrine/orm 2.14.1 3.x ...
symfony/framework-bundle 5.4.21 7.x ...
guzzlehttp/guzzle 6.5.8 7.x ...
Une dépendance avec deux versions majeures de retard, c’est un chantier en soi. Une dépendance dont le dernier commit date de plusieurs années, c’est une bibliothèque abandonnée à remplacer : je le vérifie sur son dépôt, pas sur sa page de documentation.
Les licences, ensuite. C’est le point que les acheteurs oublient, et celui que leurs avocats regretteront :
composer licenses --no-dev
npx license-checker --production --summary
├─ MIT: 612
├─ ISC: 84
├─ Apache-2.0: 31
├─ BSD-3-Clause: 18
├─ AGPL-3.0: 1
└─ UNKNOWN: 2
MIT, BSD ou Apache ne posent pas de difficulté. Une seule dépendance sous AGPL, en revanche, peut imposer de publier le code source du produit si celui-ci est fourni à des utilisateurs à travers le réseau, ce qui est exactement le cas d’un SaaS. Je ne tranche pas la question juridique : je la signale, avec le nom du paquet et l’endroit où il est utilisé, et l’avocat de l’acheteur décide. Les « UNKNOWN » se vérifient un par un.
Enfin, les secrets. Une clé d’API retirée du code reste dans l’historique git pour toujours :
gitleaks git --redact -v .
# versions plus anciennes : gitleaks detect --source . --redact -v
Finding: STRIPE_SECRET_KEY=REDACTED
RuleID: stripe-access-token
File: .env.prod
Commit: a1b2c3d
Date: AAAA-MM-JJT10:42:00Z
Une clé commitée il y a plusieurs années et jamais changée est toujours valide. Le constat n’est pas « il y a un secret dans l’historique », mais « ces clés doivent être révoquées et régénérées avant la réalisation ». C’est rapide à corriger, et je ne laisse jamais passer ce point.
5. L’analyse statique : lire le niveau, pas le résultat
Une équipe qui me dit « on a PHPStan » ne m’a encore rien dit. PHPStan a des niveaux de 0 (très permissif) à 10, Psalm a des errorLevel de 8 (permissif) à 1 (strict). Je lis d’abord la configuration :
grep -E 'level' phpstan.neon phpstan.neon.dist 2>/dev/null
grep -c 'message:' phpstan-baseline.neon 2>/dev/null
La seconde commande est la plus instructive. Une baseline enregistre les erreurs existantes pour qu’elles ne bloquent plus la CI. Un niveau 8 avec 2 400 erreurs dans la baseline, c’est un niveau 8 de façade. Je compte aussi les suppressions dans le code :
grep -rn '@phpstan-ignore\|@psalm-suppress' src | wc -l
grep -rn 'eslint-disable' assets src --include='*.js' --include='*.ts' | wc -l
Puis je lance l’outil moi-même à un niveau intermédiaire, sans baseline, pour avoir une mesure comparable d’un projet à l’autre :
vendor/bin/phpstan analyse src --level=5 --no-progress --memory-limit=1G
npx eslint . --format compact | tail -3
Le nombre d’erreurs brut m’importe peu. Je regarde leur nature : des appels de méthodes sur des valeurs qui peuvent être nulles dans le module de paiement m’inquiètent davantage que mille annotations manquantes dans le back-office.
6. Les tests et la CI : ce qui protège vraiment une mise en production
Je compte d’abord les tests, puis je les lance sur mon clone, dans un conteneur :
find tests -name '*Test.php' | wc -l
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --only-summary-for-coverage-text
Code Coverage Report Summary:
Classes: 12.40% (38/306)
Methods: 18.75% (420/2240)
Lines: 21.30% (6100/28640)
Le pourcentage global trompe dans les deux sens. Un 60 % obtenu en testant les entités et les getters ne protège rien. Un 20 % concentré sur la facturation et l’authentification protège l’essentiel. Je regarde donc la couverture des dossiers critiques, pas la moyenne. Et je note si la suite de tests passe tout simplement : une suite qui échoue depuis des mois, c’est une suite que plus personne ne lance.
Puis je lis la CI ligne à ligne (.github/workflows/*.yml, .gitlab-ci.yml). Les signaux que je cherche :
- les tests sont‑ils lancés à chaque demande de fusion, ou seulement à la main ?
- une étape est‑elle neutralisée par
continue‑on-error: true,allow_failure: trueou un|| trueen fin de commande ? Une CI toujours verte parce qu’elle ne peut pas échouer est pire qu’une absence de CI, car elle rassure ; - le déploiement est‑il automatisé, ou décrit dans un fichier texte que seule une personne sait suivre ?
- les secrets de déploiement sont‑ils dans le gestionnaire de secrets de la CI, ou en clair dans le fichier ?
grep -rnE 'allow_failure|continue-on-error|\|\| true' .gitlab-ci.yml .github/ 2>/dev/null
gh run list --limit 20 # taux d'échec des dernières exécutions, si GitHub
Quand le déploiement est manuel, je demande à le voir faire, chronomètre en main. Une mise en production de deux heures, avec des étapes à ne pas oublier, se chiffre facilement : c’est le coût de son automatisation.
7. L’infrastructure et les sauvegardes : le seul test qui compte
Pour l’infrastructure, je vérifie d’abord la propriété : à qui appartiennent le compte d’hébergement, le nom de domaine, le compte du registre DNS, les comptes des services tiers (paiement, envoi d’e-mails, stockage). Tout doit être au nom de la société, pas d’un fondateur ou d’un ancien prestataire :
whois exemple.com | grep -iE 'registrant|registrar'
dig +short NS exemple.com
Je relève aussi qui a accès en écriture à la production, les comptes d’anciens collaborateurs encore actifs, la version du système des serveurs et l’existence d’une supervision. Une équipe qui ne sait pas dire quand a eu lieu le dernier incident n’a en général ni alerte ni procédure.
Les sauvegardes, ensuite. La phrase « on a des sauvegardes tous les jours » ne vaut rien tant qu’on n’a pas restauré. Je demande à l’équipe de le faire devant moi, sur une base vierge, hors production :
createdb restore_test
time pg_restore --no-owner --dbname=restore_test sauvegarde-du-jour.dump
psql restore_test -c "SELECT count(*) FROM invoice;"
# MySQL : time mysql restore_test < sauvegarde-du-jour.sql
Je note trois choses : est-ce que ça marche, combien de temps ça prend, et est-ce que les données sont complètes (je compare quelques comptages avec la production, lus par l’équipe). Les échecs typiques : une sauvegarde de la base sans les fichiers déposés par les utilisateurs, une sauvegarde chiffrée dont personne ne retrouve la clé, ou une restauration qui prend une journée alors que l’engagement de service promet quatre heures.
8. La grille de notation
Je note chaque domaine sur 10, avec une grille fixe, pour que deux audits soient comparables et que la note ne dépende pas de mon humeur. La note est toujours accompagnée d’une phrase qui la justifie.
| Domaine | Ce que je mesure | 8 à 10 | 0 à 4 |
|---|---|---|---|
| Code | Points chauds, taille des fichiers, analyse statique et baseline | Analyse statique stricte, baseline faible, modules découpés | Fichiers géants modifiés sans cesse, baseline de milliers d’erreurs |
| Architecture | Versions, découpage, dépendances majeures, capacité à monter en charge | Versions maintenues, frontières claires | Version en fin de vie, tout dépend de tout |
| Sécurité | Audits de dépendances, secrets, accès, données personnelles | Aucune vulnérabilité élevée, secrets dans un coffre | Vulnérabilités critiques, clés valides dans l’historique |
| Tests et déploiement | Couverture des parcours critiques, CI, automatisation | Tests sur chaque fusion, déploiement en une commande | Aucun test, mise en production manuelle |
| Équipe et savoir | Bus factor, répartition des commits sur les modules critiques | Au moins deux personnes par module critique | Une seule personne porte l’essentiel |
| Documentation | Installation, déploiement, architecture, accès | Un nouveau développeur installe le projet seul en une journée | Rien d’écrit, tout est dans une tête |
Je fais un test simple pour la documentation : j’essaie d’installer le projet en suivant uniquement le README. Le temps que j’y passe, et les questions que je dois poser, sont la note.
9. Des constats aux jours de travail
Un rapport qui dit « la dette technique est importante » n’aide personne à négocier. Chaque constat devient un chantier, chaque chantier une fourchette en jours, avec ses hypothèses écrites. Par exemple :
| Constat | Chantier | Estimation | Hypothèse |
|---|---|---|---|
| Langage en fin de vie, framework deux majeures en retard | Migration du langage puis du framework | 25 à 30 jours | Par étapes, avec des tests ajoutés sur les parcours touchés |
| Bus factor de un | Transmission documentée à un second développeur | 10 jours | Le développeur clé reste disponible pendant la transmission |
| Restauration jamais testée | Procédure de restauration écrite et testée | 3 jours | Les sauvegardes existent et sont complètes |
| Déploiement manuel | Pipeline de déploiement automatisé | 8 jours | Hébergement actuel conservé |
Je chiffre en jours, jamais en euros. La raison est simple : le même chantier ne coûte pas la même chose selon qui le fait. L’équipe interne de l’acheteur, un prestataire offshore, une agence parisienne ou l’équipe actuelle de la cible n’ont ni le même tarif journalier ni la même vitesse sur ce code. C’est à l’acheteur de valoriser ces jours au coût de l’équipe qui fera réellement le travail. Mon rôle est que le nombre de jours soit juste, et que ses hypothèses soient vérifiables.
Je donne toujours une fourchette, et l’écart dit quelque chose. Un écart étroit signifie que j’ai vu le code concerné. Un écart large signifie qu’une partie reste incertaine, et je dis laquelle. Je classe aussi chaque chantier par urgence : ce qui doit être réglé avant la réalisation, ce qui doit l’être dans les six mois, et ce qui peut attendre.
10. Des jours de travail aux clauses
La dernière partie du rapport, « Pour la transaction », traduit les constats dans le vocabulaire de ceux qui négocient. Je ne rédige pas les clauses, c’est le travail des avocats, mais je leur donne la matière technique précise. Quatre leviers reviennent presque toujours :
- Le prix. Le total des jours de remise à niveau, valorisé par l’acheteur au tarif de son équipe, donne une base objective pour ajuster le prix. C’est plus solide qu’une impression générale sur la « qualité du code ».
- La garantie. Ce que je ne peux pas vérifier complètement en huit jours se couvre par une garantie spécifique : licences des dépendances, cession des droits par les freelances et les agences qui ont écrit du code, conformité du traitement des données personnelles.
- La condition suspensive. Ce qui est binaire et rapide se règle avant la réalisation de la cession : un test de restauration réussi, les comptes d’hébergement et le nom de domaine transférés au nom de la société, les clés exposées révoquées.
- La fidélisation. Quand le savoir repose sur une personne, le risque ne se corrige pas avec du code. Un dispositif de fidélisation du développeur clé, signé avant la réalisation, lui laisse le temps de transmettre ce qu’il sait.
Le rapport se termine par un plan à 30, 60 et 90 jours, qui ordonne les chantiers après la signature. Vous pouvez voir la structure complète (synthèse, notes par domaine, risques, plan et transaction) dans l’exemple de rapport, construit sur des données fictives.
Une dette chiffrée en jours se négocie. Une dette découverte après la signature se subit.
Ce que j’en retiens
Aucune de ces commandes n’est secrète. git shortlog, composer audit, gitleaks ou pg_restore sont à la portée de n’importe quelle équipe. Ce qui fait la valeur d’une due diligence, c’est de savoir ce que leurs sorties cachent : un niveau PHPStan tenu par une baseline, une CI qui ne peut pas échouer, un bus factor masqué par des alias, une couverture de tests qui protège les getters et pas la facturation, une sauvegarde qui n’a jamais été restaurée.
Et c’est de garder en tête à qui s’adresse le rapport. Les développeurs ont besoin du détail en annexe. Le comité d’investissement a besoin de trois choses : combien de jours, quand, et ce qu’il faut inscrire dans le protocole.