En bref — la sécurité applicative n'est pas une liste de cases à cocher une bonne fois pour toutes. C'est une discipline, et son principe tient en une phrase : le chemin sûr doit être le chemin par défaut, et l'insécurité doit demander un effort conscient. L'OWASP Top 10 est une carte précieuse, pas une to-do. Et c'est en pratiquant l'offensif — casser des systèmes sur TryHackMe — que j'ai vraiment appris à défendre les miens.
Mes premières années sur Laravel m'ont donné une vision faussement simple de la sécurité : il n'y avait presque rien à y penser. Les caractères spéciaux échappés automatiquement, tous les formulaires protégés contre le CSRF par défaut, les requêtes qui passaient par l'ORM… Le framework portait l'essentiel, et c'était confortable. Trop, peut-être : à ce régime, on finit par croire que la sécurité est un acquis.
C'est en cherchant à aller plus loin qu'on en mesure la vraie complexité — et la vraie fragilité. Il suffit d'une API mal pensée pour ouvrir un vecteur d'attaque, d'un module récupéré sur internet et intégré sans le lire pour embarquer une faille qu'on n'a même pas écrite. La sécurité par défaut d'un framework ne couvre que ce qu'il voit ; tout ce qu'on ajoute par-dessus, c'est à nous de le sécuriser.
Ce qui a vraiment ancré cette prise de conscience, c'est un détail glaçant de l'époque : sur GitHub, il suffisait de rechercher « remove password » dans les commits pour voir défiler des milliers d'historiques où un mot de passe disparaissait du code… en clair. Supprimé du dernier commit, certes — mais inscrit à jamais dans l'historique git, lisible par quiconque sait remonter le temps. Ce jour-là, j'ai compris que la sécurité n'est pas une fonctionnalité qu'on active : c'est une vigilance de chaque instant, et la moindre inattention laisse une trace que rien n'efface.
C'est pourquoi je me méfie autant de l'idée de checklist. Le réflexe, face à la sécurité, est de sortir l'OWASP Top 10 et de le cocher ligne à ligne : injection, cochée ; XSS, cochée ; CSRF, cochée. C'est rassurant, et c'est exactement là que le piège se referme. Une faille ne se loge pas dans la case qu'on vient de cocher — elle se loge dans le code écrit la semaine d'après, dans la fonctionnalité ajoutée après l'audit, dans le raccourci pris un vendredi soir.
Après plusieurs années à construire des applications à fort enjeu et une pratique régulière de l'offensif, ma conviction est faite : la sécurité ne se traite pas par énumération, mais par conception. Voici comment je l'aborde concrètement — non pas un catalogue de failles, mais la façon de penser qui les prévient.
L'OWASP Top 10 est une carte, pas une to-do
Le Top 10 est inestimable : c'est une cartographie partagée des endroits où les attaques se produisent, le fruit de l'observation de milliers d'incidents réels. L'erreur n'est pas de s'en servir — c'est de le traiter comme une liste finie de tâches. Un attaquant ne suit pas votre checklist ; il cherche l'écart, l'endroit que vous n'avez pas pensé à cocher parce qu'il n'existait pas encore au moment de l'audit. Une checklist remplie une fois donne un sentiment de sécurité pendant que la base de code, elle, continue d'évoluer. La carte dit où regarder ; elle ne dispense pas de la discipline qui consiste à regarder en permanence.
Le principe directeur : sécurisé par défaut
Une seule idée organise toute ma pratique : le chemin sûr doit être le chemin par défaut, et l'insécurité doit demander un effort actif et visible. La protection ne peut pas reposer sur la vigilance de chaque développeur à chaque ligne — c'est une garantie qui finit toujours par céder. Elle doit être structurelle. J'applique déjà ce principe à l'isolation multi-tenant, avec un scope global qui rend l'oubli d'un filtre impossible par défaut. La même logique se décline faille par faille.
L'injection : la requête paramétrée par défaut, le SQL brut comme exception
On ne construit jamais une requête en concaténant une entrée utilisateur. L'ORM ou les requêtes paramétrées sont la norme ; le SQL brut est une exception qu'on justifie et qu'on relit.
// ❌ Concaténation : la porte ouverte à l'injection.
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// ✅ Requête paramétrée : la donnée n'est jamais interprétée comme du code.
db.query('SELECT * FROM users WHERE email = $1', [email]);La règle n'est pas « pense à échapper » — c'est « rends la forme dangereuse si inhabituelle qu'elle saute aux yeux en relecture ». Une protection qui dépend de la mémoire d'un développeur fatigué n'est pas une protection.
Le XSS : se méfier des trappes d'échappement
Les frameworks modernes échappent la sortie par défaut — Blade, React, Astro encodent le HTML automatiquement. Le XSS ne vient donc presque jamais du templating normal : il vient des trappes d'échappement, ces échappatoires qui désactivent la protection. {!! !!} en Blade, dangerouslySetInnerHTML en React, set:html en Astro, v-html en Vue. Chacune est une décision de sécurité, pas une commodité. Je traite chaque trappe comme une ligne qui doit se justifier — et si elle affiche du contenu d'origine utilisateur, elle passe par une sanitisation stricte.
Un exemple honnête : ce site affiche le corps de ses articles avec set:html, à partir du contenu servi par mon CMS. C'est acceptable parce que ce contenu est rédigé par moi — une source de confiance — et jamais alimenté par un visiteur. Toute la sécurité tient dans cette distinction : la trappe n'est pas dangereuse en soi, elle l'est selon qui contrôle ce qui la traverse. Le jour où ce contenu deviendrait modifiable par un tiers, la même ligne deviendrait une faille.
CSRF et sessions : les réglages qu'on ne voit pas
Beaucoup de la sécurité se joue dans des drapeaux invisibles. Pour le CSRF : un token sur chaque requête qui modifie un état, et des cookies en SameSite. Pour l'authentification : un hachage avec bcrypt ou argon2 (on ne « chiffre » jamais un mot de passe, on le hache), la régénération de l'identifiant de session à la connexion pour couper la fixation de session, et une limitation de débit sur les endpoints sensibles.
// Les drapeaux invisibles qui font tout le travail.
res.cookie('session', token, {
httpOnly: true, // inaccessible au JS → vol par XSS bloqué
secure: true, // transmis sur HTTPS uniquement
sameSite: 'lax', // limite l'envoi cross-site → frein au CSRF
});Ces trois drapeaux ne coûtent rien et bloquent des classes entières d'attaques. Les oublier ne casse rien de visible — c'est bien ce qui les rend faciles à négliger.
Ne jamais faire confiance à l'entrée — et prévoir le filet
Toute donnée qui vient du client est hostile jusqu'à preuve du contraire. On la valide à la frontière, systématiquement, avant qu'elle ne pénètre la logique métier. Mais la validation applicative peut être contournée ou oubliée — d'où la défense en profondeur. Sur du multi-tenant, je double l'isolation applicative d'une Row Level Security au niveau PostgreSQL : même une requête qui oublierait son filtre se heurte au refus de la base. On empile les couches parce qu'une seule, tôt ou tard, finit par lâcher. La sécurité robuste n'est pas un mur, c'est une succession de murs.
Ce que l'offensif m'a appris sur le défensif
On défend mal ce qu'on n'a jamais attaqué. Ma pratique régulière sur TryHackMe a changé ma façon de relire mon propre code. Avant, je relisais en demandant « est-ce que ça marche ? ». Maintenant, je relis en demandant « comment je casserais ça ? ». C'est un renversement de posture : adopter le point de vue de l'assaillant le temps d'une relecture, traquer l'hypothèse implicite, l'entrée non prévue, le cas limite qu'on a « oublié » de gérer. Comprendre comment une faille s'exploite rend instantanément plus rigoureux sur la façon de l'empêcher.
Ce regard, je l'applique aussi au code généré par IA. Un agent produit du code plausible, qui a l'air correct — c'est précisément ce qui le rend dangereux. Il mérite le même examen offensif que le mien, ni plus ni moins : du code reste du code à sécuriser, peu importe qui l'a écrit.
La sécurité ne se rajoute pas à la fin
La faille la plus coûteuse est celle qu'on découvre après la mise en production. La sécurité est une contrainte de conception, qui se pense dès la modélisation : quelles données sont sensibles, où passe la frontière de confiance, que se passe-t-il si cette entrée est malveillante. Greffer la sécurité sur un système déjà fini coûte toujours plus cher et protège toujours moins bien que de l'avoir intégrée dès le départ. C'est aussi pour cela que je vérifie les contraintes — réglementaires, d'isolation — avant de choisir une architecture, jamais après.
La sécurité applicative n'est pas un état qu'on atteint, c'est une discipline qu'on entretient. Pas de case finale à cocher : des réflexes ancrés, un principe — sécurisé par défaut — et une veille permanente, parce que les attaques évoluent et que le code, lui, n'arrête jamais de bouger. Le jour où l'on se croit « sécurisé », on a déjà baissé la garde.