Aller au contenu

Comment revoir le code de sécurité au-delà du linter

Comment revoir le code de sécurité au-delà du linter

Section intitulée « Comment revoir le code de sécurité au-delà du linter »

Un linter repère des motifs locaux : une API dangereuse, une dépendance vulnérable, une variable jamais utilisée. Une revue orientée sécurité cherche autre chose : les hypothèses implicites, les frontières de confiance mal placées et les invariants métier qui sautent dès qu’une requête est forgée à la main.

→ Lire l’explication complète de la marmotte

Le compromis : vitesse de livraison contre raisonnement sur les frontières

Section intitulée « Le compromis : vitesse de livraison contre raisonnement sur les frontières »

La revue de sécurité coûte du temps de lecture, de discussion et parfois de refonte. Elle ralentit une fusion de code lorsque le modèle d’autorisation est ambigu, lorsqu’une API expose trop de données ou lorsqu’un traitement asynchrone perd le contexte de sécurité. C’est le prix à payer pour éviter que la production devienne un environnement de test hostile, avec des attaquants plus créatifs que les jeux de données de recette.

La position à tenir est nette : automatiser tout ce qui est syntaxique, réserver la revue humaine aux frontières de confiance et aux décisions métier. Faire relire chaque renommage de variable par une personne sécurité est une taxe absurde. Laisser passer sans revue un endpoint qui modifie le rôle d’un compte parce que le linter n’a rien signalé est une invitation gravée sur métal.

Une revue sécurité doit être obligatoire lorsque le changement touche au moins un de ces éléments :

  • une identité, une session, un jeton ou une délégation d’accès ;
  • une entrée contrôlée par un utilisateur, un partenaire ou un système externe ;
  • une requête vers une base de données, un système de fichiers, un réseau ou un interpréteur ;
  • un secret, une clé de chiffrement, un certificat ou une donnée personnelle ;
  • une règle métier qui sépare deux comptes, deux organisations ou deux niveaux de privilège ;
  • une file de messages, un traitement différé ou un mécanisme de reprise.

Les outils SAST (Static Application Security Testing), c’est-à-dire l’analyse statique du code, excellent pour signaler des familles connues de défauts. Ils ne savent pas déterminer si tenantId doit venir du jeton signé plutôt que du corps HTTP, ni si un remboursement doit être idempotent. Un outil ne lit pas la règle métier cachée dans une phrase de ticket. Il lit des arbres syntaxiques. C’est déjà beaucoup, mais ce n’est pas de la télépathie.

Les dépendances de revue sont aussi concrètes que celles d’une compilation : langage et version du runtime, framework HTTP, bibliothèque d’authentification, moteur de persistance, format des événements, schéma de données et politique de journalisation. Sans cette cartographie, une revue se résume à commenter des lignes isolées. Vingt ans de métier apprennent que le bug le plus coûteux se cache rarement dans la ligne commentée avec beaucoup d’assurance.

Pour cadrer les flux d’identité, la page Identités et contrôle d’accès complète utilement cette revue. Pour les propriétés cryptographiques qui ne se devinent pas dans un appel de bibliothèque, voir Cryptographie appliquée.

Les défauts de logique qu’un linter ne peut pas prouver

Section intitulée « Les défauts de logique qu’un linter ne peut pas prouver »

Le défaut le plus fréquent n’est pas une injection SQL. C’est une autorisation vérifiée sur le mauvais objet. Une application charge une facture par son identifiant, vérifie que l’appelant est authentifié, puis retourne la facture. Chaque ligne semble respectable. L’invariant absent est pourtant vital : la facture doit appartenir à l’organisation portée par l’identité appelante.

Cette classe s’appelle IDOR (Insecure Direct Object Reference), référence directe non sécurisée à un objet. Le correctif n’est pas d’ajouter une vérification décorative après coup. La requête de chargement doit intégrer la portée d’autorisation : chercher la facture dans l’organisation autorisée, jamais chercher une facture globale puis espérer ne pas oublier le contrôle suivant.

Les validations d’entrée ont le même piège. Vérifier qu’un champ redirect_uri est une URL valide ne prouve pas qu’il pointe vers une destination autorisée. Vérifier qu’un nom de fichier ne contient pas ../ ne prouve pas qu’un lien symbolique ne mène pas hors du répertoire prévu. Vérifier qu’un rôle appartient à une liste ne prouve pas que l’appelant a le droit de l’attribuer. La validation de forme et la validation d’autorisation répondent à deux questions différentes. Les confondre produit des correctifs qui rassurent les tableaux de bord et très peu le reste.

Les parcours temporels demandent une attention particulière. Un contrôle effectué avant la mise en file d’un travail ne protège pas nécessairement le consommateur qui s’exécute plus tard. Entre les deux, un compte peut être désactivé, une délégation retirée ou un objet transféré. Le consommateur doit décider si l’autorisation est liée à l’instant de soumission ou à l’instant d’exécution, puis conserver les informations nécessaires pour appliquer cette décision. Une permission sans date ni contexte finit souvent par devenir une permission éternelle. C’est pratique pour les démons, moins pour les systèmes d’information.

La revue doit aussi rechercher les écarts entre interfaces. Une règle appliquée dans l’interface web mais absente de l’API (Application Programming Interface) est absente. Un filtre de propriété appliqué à GET /users mais pas à l’export CSV est absent. Une limitation de débit appliquée au navigateur mais pas à un endpoint machine est une décoration. Les contrôles de sécurité doivent être placés au point de décision commun, pas éparpillés dans les écrans.

Revoir les frontières de confiance et les données non fiables

Section intitulée « Revoir les frontières de confiance et les données non fiables »

Toute donnée provenant d’une requête HTTP, d’un cookie, d’un en-tête, d’un message, d’un fichier importé, d’une variable d’environnement ou d’un service tiers est non fiable jusqu’à validation explicite. Les variables d’environnement méritent cette méfiance : elles sont souvent traitées comme sacrées parce qu’elles vivent dans un fichier de déploiement. Elles restent modifiables par toute identité capable de modifier ce déploiement.

La SSRF (Server-Side Request Forgery), falsification de requête côté serveur, illustre bien ce que le code local masque. Une fonction qui télécharge une image depuis une URL fournie peut sembler bénigne. Elle devient un pivot réseau si elle atteint des adresses internes, des services de métadonnées cloud ou une console d’administration. Le linter voit une requête HTTP. La revue doit exiger une liste d’hôtes autorisés, une résolution DNS contrôlée, le blocage des plages privées après résolution, un délai court, une taille maximale et l’interdiction des redirections hors périmètre.

Les désérialisations demandent le même niveau de suspicion. Reconstruire un objet à partir de données contrôlées peut déclencher du code, écraser des attributs ou contourner des validations. Préférer des formats déclaratifs, limiter les types acceptés et traduire la donnée entrante vers un modèle interne. Accepter un objet complet parce que le framework sait le faire, c’est confier les clés du bâtiment au formulaire de contact. Les frameworks adorent aider. Ils aident parfois beaucoup trop.

Les journaux sont également une frontière. Une erreur brute peut contenir un mot de passe, un jeton d’accès, une requête signée ou un identifiant de session. La revue doit vérifier deux flux distincts : ce qui revient au client et ce qui part vers les logs. Le client reçoit un identifiant d’erreur corrélable, pas une trace interne. Les logs conservent le contexte utile sans enregistrer de secret brut. Cette séparation doit être testée, car une exception ajoutée à la hâte a l’élégance d’un extincteur rempli d’essence.

Pour les contraintes d’exposition réseau et de proxy, Réseaux et services exposés fournit le cadre d’architecture nécessaire. Une revue de code ne remplace pas une segmentation réseau ou une politique de sortie restrictive.

Exemple : empêcher une élévation de privilège par masse d’attributs

Section intitulée « Exemple : empêcher une élévation de privilège par masse d’attributs »

Le mass assignment, affectation massive d’attributs, apparaît lorsqu’un framework recopie les champs reçus dans un objet persistant. Le champ role peut alors passer de user à admin si aucun modèle d’entrée ne l’exclut. Une liste noire est fragile : chaque nouvelle propriété sensible devient une future omission. Une liste blanche est la décision correcte.

// Express 5 et Zod 3 : ne jamais persister req.body directement.
// Le rôle provient exclusivement d'une règle serveur.
import { z } from "zod";
const profileInput = z.object({
displayName: z.string().min(1).max(80),
locale: z.enum(["fr-FR", "en-US"]),
}).strict();
app.patch("/api/profile", requireAuthenticated, async (req, res) => {
const input = profileInput.parse(req.body);
await users.update(
{ id: req.auth.subjectId }, // Identité issue de la session vérifiée.
{ displayName: input.displayName, locale: input.locale }
);
res.status(204).end();
});

Ce code dépend de trois conditions : requireAuthenticated doit vérifier la signature et l’expiration de la session, subjectId ne doit jamais provenir du corps HTTP, et users.update doit imposer l’unicité de l’identifiant ciblé. La revue doit ouvrir ces trois fonctions. Lire uniquement le contrôleur revient à inspecter une serrure sans regarder si la porte a été montée sur des charnières.

Un test de sécurité doit accompagner le correctif : soumettre {"role":"admin"} et vérifier que la requête est rejetée, soumettre un identifiant de profil étranger et vérifier qu’aucune ligne n’est modifiée. Les tests de non-régression doivent viser le refus observable, pas seulement le chemin autorisé.

Secrets, dépendances et conditions d’exploitation

Section intitulée « Secrets, dépendances et conditions d’exploitation »

Le code ne garantit pas la sécurité d’un secret injecté dans un environnement ouvert à trop d’identités. Une revue doit identifier l’origine du secret, les identités capables de le lire, les copies créées par la chaîne CI/CD (Continuous Integration and Continuous Delivery), intégration et livraison continues, et les traces où il peut fuiter.

Un secret de production ne doit jamais être une variable stockée dans le dépôt, un argument de ligne de commande ou une valeur affichée au démarrage. Les arguments sont fréquemment visibles dans la liste des processus. Les variables d’environnement peuvent être collectées par des outils de diagnostic. Les logs finissent dans des systèmes où la rétention se compte en mois, parce qu’apparemment l’éternité est une politique de conservation.

La rotation doit être conçue dans le protocole applicatif. Une clé ou un jeton rotatif nécessite une période où ancienne et nouvelle valeur sont acceptées, un identifiant de version, une date de bascule et une révocation documentée. Remplacer une valeur à chaud sans double lecture casse les instances qui n’ont pas encore rechargé leur configuration. Accepter indéfiniment les deux valeurs transforme une rotation en collection archéologique.

Les dépendances exigent une revue de leur usage, pas seulement de leur numéro de version. Une bibliothèque corrigée ne protège pas un appel qui désactive la vérification de certificat, accepte n’importe quel algorithme de signature ou suit toutes les redirections. Générer un inventaire SBOM (Software Bill of Materials), inventaire des composants logiciels, aide à réagir à une vulnérabilité publiée. Il ne prouve ni que la fonction vulnérable est atteignable, ni que la configuration est saine.

La supervision doit permettre de détecter les refus anormaux : échecs d’autorisation, erreurs de validation, appels sortants bloqués, accès à des ressources absentes du périmètre et changements de privilèges. Conserver l’identifiant de corrélation, l’identité technique et le résultat de décision, sans stocker le jeton lui-même. Sans ces événements, une revue découvre peut-être le défaut, mais personne ne peut établir son exploitation.

Une revue ne prouve pas qu’une infrastructure applique les politiques supposées par le code. Si l’application exige HTTPS (Hypertext Transfer Protocol Secure) mais qu’un proxy accepte le trafic en clair sur un réseau partagé, la propriété s’arrête au dépôt. Si un compte de service possède des privilèges administrateur globaux, une vérification applicative fine ne répare pas ce choix.

Elle ne garantit pas non plus la disponibilité. Une limite de taille dans le code ne compense pas une absence de quota sur un stockage objet. Un délai sur un appel sortant ne protège pas contre la saturation de tous les workers. Une validation métier n’empêche pas un rejeu si l’API ne possède ni clé d’idempotence ni enregistrement transactionnel du résultat.

La revue doit donc produire des exigences vérifiables hors code : règles de réseau, politiques d’identité, rotation des secrets, sauvegardes testées, restauration contrôlée et alertes actionnables. Les sauvegardes chiffrées ne suffisent pas si aucune procédure ne restaure les clés nécessaires. Un coffre fermé dont la combinaison a disparu reste très performant pour protéger les données de leurs propriétaires.

Sans revue orientée sécurité, la validation d’une fusion repose sur un signal purement syntaxique : un rapport vert devient une preuve implicite d’innocuité, alors qu’il n’atteste que l’absence de motifs connus. Les décisions de conception restent alors non écrites, et rien ne relie un changement d’autorisation à la règle métier qui le justifiait. Lors d’une analyse d’incident, cette absence de trace empêche de dater l’introduction d’un défaut et de distinguer un comportement voulu d’une régression. Le coût se déplace aussi dans le temps : une portée d’accès oubliée à la conception se corrige ensuite sur plusieurs interfaces, migrations de données et intégrations existantes. Enfin, aucun test de refus n’est exigé, donc les chemins interdits ne sont jamais exercés et une protection retirée par inadvertance passe inaperçue jusqu’à sa découverte externe.

Un contrôle automatique de code cherche des erreurs connues dans chaque fichier. Il peut signaler une fonction dangereuse ou une bibliothèque vulnérable, mais il ne connaît pas toujours les règles propres au service. Une revue de sécurité lit donc le changement avec les règles d’accès, les données manipulées et les systèmes appelés.

La vérification commence par identifier qui envoie une demande et quelles données peuvent être modifiées. Il faut ensuite vérifier que le programme utilise l’identité vérifiée par le serveur, pas un identifiant fourni dans la demande. Chaque accès à une facture, un compte, un document ou une action sensible doit être limité au bon propriétaire ou à la bonne organisation.

La revue examine aussi les données venant de l’extérieur. Une adresse, un fichier ou un message peut contenir une valeur prévue pour faire agir le serveur contre son intérêt. Le programme doit limiter ce qu’il accepte, contrôler sa destination et ne jamais enregistrer directement des champs sensibles reçus d’un utilisateur.

Enfin, la sécurité dépend de l’exploitation : mots de passe techniques, clés de chiffrement, journaux, sauvegardes et alertes. Si une clé fuit dans un journal, si un ancien accès reste accepté ou si une sauvegarde ne peut pas être restaurée, le code correct ne suffit plus. Quand une vérification échoue, le programme doit refuser l’action, enregistrer un événement sans secret, puis permettre une investigation.

À lire aussi

Pages voisines