Go et SQL : six trous d'injection que l'ORM ne bouche pas
Ou : Comment écrire des requêtes paramétrées partout, sauf aux endroits qui comptent
L’injection SQL en Go a une réputation de problème réglé. Le paquet database/sql sépare la requête de ses paramètres, GORM échappe les arguments, pgx utilise le protocole PostgreSQL, et tout le monde a lu un jour qu’il ne fallait pas coller une saisie utilisateur dans une chaîne SQL. Le problème, c’est que chacune de ces protections couvre les valeurs passées comme arguments, et rien d’autre. Un nom de colonne pour un tri, une liste d’identifiants ou un raccourci d’ORM qui accepte une chaîne : là, la requête redevient du texte, et le texte n’est jamais vérifié.
Le point de départ est un article publié le 29 septembre 2026 sur freeCodeCamp, « How ORMs Still Let SQL Injection Through », qui recense quatre trous dans Sequelize, Prisma et TypeORM1. L’article parle de JavaScript et de TypeScript. La question évidente pour quelqu’un qui écrit du Go est de savoir si le langage, avec son typage strict et sa bibliothèque standard prudente, échappe au problème.
Réponse courte : non. Les quatre trous existent tels quels. GORM et database/sql ajoutent deux pièges particulièrement faciles à rater. Enfin, une ancienne faille de pgx, corrigée dans la branche v4, rappelle que le mécanisme de paramétrage dépend aussi du driver. Cela donne six trous applicatifs encore possibles aujourd’hui, plus un septième cas historique dans la couche qui transporte les paramètres.
Injection SQL en Go : ce que GORM, sqlx, Bun et pgx ne protègent pas
Avant d’entrer dans le détail, une précision sur la méthode. Les trous n°1 à 6 et les règles de gosec ont été vérifiés en exécutant du code, le 30 septembre 2026, avec Go 1.27.1, GORM 1.31.2, pgx 5.11.0, sqlx 1.4.0, Squirrel 1.5.4, Bun 1.2.18 et gosec 2.29.0, sur PostgreSQL 17 dans un conteneur jetable et sur SQLite avec le driver pur Go glebarez/sqlite.
Les comportements de interpolateParams, bun.In, de la requête sqlc, de govulncheck et de CVE-2024-27289 n’ont pas été reproduits dans cette série de tests. Ces passages reposent sur les documentations et avis de sécurité cités. Les versions comptent : un résultat expérimental n’est valable que pour le code et la configuration réellement exécutés.
Trou n°1 : la requête brute assemblée à la main
C’est le cas d’école, celui que tout le monde croit avoir éliminé. Une requête construite avec fmt.Sprintf ou une concaténation envoie la saisie de l’utilisateur au serveur comme si elle faisait partie du code SQL.
| |
Une valeur comme celle-ci :
| |
produit une requête dont la condition devient vraie pour toutes les lignes :
| |
La correction est connue, et la documentation officielle de Go sur l’injection SQL la résume en une règle : passer les valeurs comme arguments des fonctions du paquet sql, jamais les formater dans la chaîne2.
| |
Le symbole change selon le driver et la bibliothèque, mais le principe reste le même partout.
« Mais j’utilise
Rawseulement quand GORM ne sait pas exprimer ma requête. »C’est précisément la raison d’être de
Raw. Le problème n’est pas la requête brute. Le problème est de lui confier une chaîne dans laquelle le SQL et les données ont déjà été mélangés.
Un des déclencheurs les plus fréquents est la recherche avec LIKE. On veut entourer la saisie de %, donc on fabrique spontanément la chaîne entière :
| |
Pourtant, les jokers peuvent faire partie de la valeur liée :
| |
La concaténation existe toujours, mais elle ne construit plus du SQL. Elle construit la valeur envoyée dans le placeholder. C’est une différence de deux caractères dans le code et de toute la frontière de sécurité dans le résultat.
Il reste une question distincte : faut-il laisser l’utilisateur employer % et _ comme jokers ? Ce n’est plus une injection SQL, puisque la structure de la requête ne change pas, mais cela peut élargir la recherche. Si l’interface promet une recherche littérale, il faut échapper ces caractères selon le SGBD et déclarer le caractère d’échappement.
La documentation de GORM montre la forme Where("name LIKE ?", "%jin%")3. Le placeholder protège la frontière SQL. L’échappement éventuel des jokers applique la règle fonctionnelle de la recherche. Mélanger les deux conduit souvent soit à une injection, soit à une fonction de nettoyage maison qui refuse les apostrophes aux utilisateurs dont le nom en contient.
À retenir : LIKE ne justifie pas de concaténer la requête. Construis le motif dans l’argument, pas dans le SQL.
Trou n°2 : les identifiants qu’un placeholder ne représente pas
Les paramètres protègent les valeurs. Ils ne représentent pas les noms de tables, les noms de colonnes, les mots-clés, les opérateurs ou la direction d’un tri. La base comprend ceci :
| |
Mais ceci ne signifie pas « trie selon la colonne contenue dans $1 » :
| |
Le problème apparaît dès qu’une API laisse le client choisir le tri :
| |
La page Security de GORM répertorie plusieurs méthodes dont certaines entrées ne sont pas échappées : Select, Distinct, Pluck, Group, Having, Raw, Exec, Order, Table et plusieurs variantes de Joins4. Dans Squirrel, OrderBy(string) ajoute également la chaîne à la requête.
La charge utile spectaculaire serait :
| |
Son exécution exacte dépend du SGBD et du driver. Ce détail ne sauve pas l’application. Même si une seconde instruction est refusée, l’utilisateur contrôle encore une expression à un emplacement prévu pour une colonne.
La correction n’est pas de retirer les caractères suspects. Elle consiste à ne jamais envoyer la chaîne reçue :
| |
La clé publique pointe vers une colonne écrite dans le programme. La saisie de l’utilisateur ne touche jamais le SQL.
Avec pgx, pgx.Identifier{column}.Sanitize() cite correctement un identifiant5. Avec Bun, bun.Ident(column) joue le même rôle6. Cela ne remplace pas la liste blanche. Une colonne password_hash correctement citée reste une colonne que l’API ne devrait probablement pas exposer au tri.
La citation répond à cette question : « Cette chaîne peut-elle être représentée comme un identifiant SQL ? » La liste blanche répond à une autre question : « L’utilisateur est-il autorisé à choisir cet identifiant ? »
À retenir : une valeur se lie ; un identifiant se choisit dans une liste fermée. Le citer ne suffit pas à l’autoriser.
Trou n°3 : l’injection de second ordre
Celle-ci passe facilement en revue de code parce que la première requête est irréprochable.
| |
GORM lie la valeur. Un nom comme celui-ci est stocké comme du texte :
| |
Puis, ailleurs dans l’application :
| |
La première requête était sûre. La seconde ne l’est pas. Le passage par la base n’a pas changé la provenance de la donnée ; il a seulement retardé son utilisation.
« Mais cette valeur vient de notre base. »
Oui. Et avant cela, elle venait de l’utilisateur. La base est un lieu de stockage, pas une cérémonie de purification.
La correction reste exactement la même :
| |
Il faut appliquer la même règle aux valeurs venues d’un cache, d’un fichier importé, d’une file de messages ou d’une API partenaire. La confiance ne dépend pas de l’endroit où la donnée se trouve maintenant, mais de la personne ou du système qui pouvait la choisir à l’origine.
À retenir : une donnée stockée proprement reste une donnée. Elle doit encore être liée à chaque réutilisation.
Trou n°4 : le SQL brut caché dans un appel ORM normal
Le premier trou était honnête : il utilisait une méthode appelée Raw. Celui-ci se cache dans un appel qui ressemble à du GORM ordinaire :
| |
Find n’est pas en cause. gorm.Expr reçoit déjà une expression SQL terminée et l’insère comme telle. Une valeur 0 OR 1=1 transforme le filtre en condition toujours vraie, sans point-virgule ni seconde instruction.
Les variantes à rechercher comprennent :
Where(fmt.Sprintf(...));gorm.Expr(...);clause.Expr{SQL: ...};Having,NotetOrlorsqu’ils reçoivent une chaîne formatée ;sq.Expr(fmt.Sprintf(...))avec Squirrel ;bun.Safe(x)avec Bun.
Squirrel n’est pas dangereux par nature. La forme suivante garde le fragment constant et lie l’argument :
| |
Même chose avec GORM :
| |
La conversion numérique applique le contrat métier. Le placeholder applique la séparation SQL. Les deux sont utiles, mais ils ne sont pas interchangeables : une validation réussie n’autorise pas à reconstruire ensuite la requête à la main.
Le cas de Bun mérite d’être dit sans détour : bun.Safe(x) n’échappe pas x. La méthode indique à Bun que l’appelant garantit déjà que la chaîne peut être insérée telle quelle6. Son nom décrit l’affirmation du développeur, pas une propriété vérifiée par la bibliothèque.
À retenir : traite Expr, Safe et les autres sorties de secours exactement comme Raw, même si l’appel extérieur ressemble à une requête ORM normale.
Trou n°5 : First reçoit une chaîne et GORM y voit du SQL
GORM permet de passer directement une clé primaire à First :
| |
La commodité devient dangereuse quand l’identifiant arrive de l’URL. r.PathValue, comme r.URL.Query().Get, renvoie une chaîne. Or GORM peut interpréter une chaîne passée dans cette position comme une condition SQL :
| |
La documentation de sécurité de GORM donne cet exemple :
| |
Même si le driver refuse les instructions empilées, 1=1 peut déjà faire récupérer une ligne arbitraire. L’échec éventuel de DROP TABLE n’annule pas rétroactivement le contournement de la condition.
La documentation avertit aussi à propos de FirstOrCreate, FirstOrInit, Last et Take, ainsi que des formes de Find où une chaîne non fiable devient une condition4.
Pour une clé numérique, la correction est de convertir avant l’appel :
| |
Pour un UUID ou une vraie clé textuelle, il faut rendre la condition explicite :
| |
« Le compilateur accepte pourtant la chaîne. »
Bien sûr. La signature l’accepte. Le compilateur vérifie le contrat Go, pas le sens SQL que GORM donnera au type concret à l’exécution.
À retenir : ne passe jamais directement une chaîne non fiable comme condition en ligne. Convertis-la ou lie-la dans une condition explicite.
Trou n°6 : IN (...) et le slice que database/sql ne déplie pas
Une API reçoit souvent plusieurs identifiants :
| |
L’intention SQL est simple :
| |
Mais database/sql ne transforme pas un slice arbitraire en un nombre variable de placeholders. Ce code ne devient pas automatiquement IN ($1, $2, $3) :
| |
Le réflexe suivant consiste à reconstruire la liste :
| |
Le programme a commencé avec plusieurs valeurs et termine avec un fragment SQL. strings.Join n’a rien validé ; il a seulement rangé les morceaux avec des virgules.
Avec pgx : ANY
PostgreSQL accepte un tableau comme paramètre unique :
| |
= ANY($1) compare id aux éléments du tableau7. Le slice reste un argument ; il ne devient jamais du texte SQL.
Avec sqlx : sqlx.In, puis Rebind
sqlx.In crée autant de placeholders que nécessaire et renvoie la nouvelle liste d’arguments :
| |
sqlx.In attend d’abord le placeholder ?. Rebind le convertit ensuite vers la syntaxe du driver, par exemple $1, $2 et $3 pour PostgreSQL8.
Avec GORM et Bun
GORM sait développer le slice dans cette forme :
| |
Bun documente un mécanisme comparable avec bun.In(ids)6 :
| |
Cet exemple Bun provient de la documentation ; il ne faisait pas partie des comportements exécutés pendant les tests du 30 septembre.
Il reste un cas à définir : la liste vide. Selon le SGBD et la bibliothèque, IN () peut être invalide ou produire un comportement différent de celui attendu. Le code doit décider explicitement : retourner immédiatement zéro résultat, supprimer le filtre si le métier le permet, ou ajouter une condition toujours fausse écrite par l’application.
À retenir : database/sql ne déplie pas un slice. Utilise ANY, sqlx.In, l’expansion de GORM ou bun.In, jamais strings.Join sur des valeurs externes.
Cas n°7 : la faille historique du driver pgx
Les six premiers cas viennent du code applicatif. Le septième se situe plus bas, dans le driver. Il ne s’agit pas d’un trou actuel de GORM ni d’un comportement reproduit pendant les tests de cet article, mais de CVE-2024-27289, publiée en mars 2024 et corrigée dans pgx v4.18.2.
MySQL et interpolateParams=true
Avant pgx, un détour par MySQL permet de voir pourquoi le mode de transport compte. Avec go-sql-driver/mysql, l’option interpolateParams=true remplace les placeholders côté client. Le driver fabrique une chaîne SQL complète afin d’éviter les allers-retours supplémentaires de préparation, d’exécution et de fermeture d’une instruction :
| |
L’option n’est pas une injection en elle-même. Le driver échappe les arguments. Son README précise toutefois qu’elle est refusée avec BIG5, CP932, GB2312, GBK et SJIS, car ces encodages multioctets pourraient permettre une injection9.
Ce comportement n’a pas été reproduit dans les tests de l’article. La conclusion vient de la documentation du driver : n’active pas l’option sans besoin mesuré, maîtrise l’encodage négocié et maintiens la dépendance à jour.
pgx et CVE-2024-27289
CVE-2024-27289 exigeait plusieurs conditions simultanées :
- le protocole simple, qui n’était pas le mode par défaut, devait être utilisé ;
- un signe
-devait se trouver immédiatement devant un placeholder numérique ; - un placeholder de chaîne devait suivre sur la même ligne ;
- les deux valeurs devaient être contrôlées par l’attaquant.
Les valeurs pouvaient alors former --, commencer un commentaire SQL et neutraliser le reste de la ligne. La fiche GO-2024-2605 référence github.com/jackc/pgx et github.com/jackc/pgx/v4, et documente le correctif de la branche v4 en version 4.18.210. Elle ne permet pas d’affirmer que la branche v5 était touchée, donc cet article ne le fait pas.
Aucun test de cette CVE n’a été réalisé ici. La version pgx 5.11.0 mentionnée au début correspond seulement à l’environnement utilisé pour les trous n°1 à 6. Elle ne constitue ni une reproduction de la faille, ni une validation expérimentale du correctif.
Ce cas historique constitue bien le septième cas étudié, mais pas un septième trou actuel de GORM. Il montre que même une application qui sépare correctement la requête et ses paramètres dépend encore du code qui les encode et les transporte.
À retenir : les paramètres restent la bonne abstraction, mais la version du driver et son mode de protocole appartiennent aussi au modèle de menace.
Ce que le typage de Go évite vraiment
r.URL.Query().Get renvoie une chaîne. Pour en faire un entier, un flottant ou une durée, il faut passer explicitement par strconv ou une fonction de conversion. Cette étape crée un endroit naturel pour appliquer les limites du métier.
Elle évite aussi certaines ambiguïtés des parseurs dynamiques : la valeur renvoyée par Get ne devient pas spontanément un tableau ou un objet parce que le client a ajouté des crochets au nom du paramètre.
Mais le compilateur ne connaît pas SQL. Ces deux lignes sont des opérations parfaitement valides sur des chaînes :
| |
La première construit une valeur destinée à un placeholder. La seconde construit le programme SQL. Le système de types ne voit pas la frontière parce que, dans les deux cas, le résultat est un string.
C’est là qu’un outil comme sqlc change l’architecture. Les requêtes sont écrites dans des fichiers .sql, puis transformées en méthodes Go typées11 :
| |
Le code ordinaire n’a plus besoin de construire WHERE, ce qui réduit fortement la surface des trous n°1 et n°4 pour les requêtes qui passent réellement par sqlc.
La limite reste ORDER BY. Un placeholder ne peut pas devenir une colonne, même avec du code généré. Pour quelques tris connus, une possibilité consiste à utiliser plusieurs requêtes statiques ou un CASE sur une valeur liée :
| |
Cet exemple illustre le principe, mais il n’a pas été compilé ni mesuré pendant les tests. Le paramètre choisit une branche déjà écrite ; il ne devient jamais un identifiant. Plusieurs requêtes nommées peuvent être plus simples à lire et à optimiser.
Ce que gosec 2.29.0 a trouvé, et surtout ce qu’il n’a pas trouvé
Les règles pertinentes de gosec sont :
- G201, pour une requête SQL construite avec une chaîne de format ;
- G202, pour une requête SQL construite par concaténation ;
- G701, pour l’analyse de propagation d’une donnée non fiable vers une exécution SQL12.
Le résultat important des tests n’est pas seulement ce que gosec sait reconnaître. C’est son angle mort : gosec 2.29.0 n’a signalé aucun des cinq appels GORM vulnérables testés : Where avec fmt.Sprintf, Order, First avec une chaîne, Raw avec fmt.Sprintf et Exec avec fmt.Sprintf.
Ce résultat ne permet pas de conclure que gosec ignore toutes les formes équivalentes. Il décrit uniquement les cinq appels exécutés pendant le test. gorm.Expr reste un point sensible à relire manuellement, mais il ne faisait pas partie de cette mesure.
Le test G202 a porté sur une concaténation écrite directement dans db.Query :
| |
Il ne permet pas d’affirmer qu’une variante avec une variable intermédiaire aurait été signalée de la même façon. La documentation de gosec décrit G201 et G202 pour database/sql, mais le résultat de cet article reste limité aux formes réellement exécutées13.
Une recherche textuelle complète donc le scanner :
| |
Le but n’est pas d’obtenir zéro résultat. Une application peut légitimement appeler Raw, Order ou Expr. Le but est que chaque résultat réponde clairement à trois questions :
- D’où vient chaque partie dynamique ?
- S’agit-il d’une valeur, d’un identifiant ou d’un fragment SQL ?
- Quelle API garantit la liaison, la citation ou la sélection dans une liste fermée ?
La campagne de tests elle-même mérite un lien avec les tests d’intégration Go exécutés contre un vrai PostgreSQL. Une injection dépend parfois du dialecte, du driver et de sa configuration. Un mock peut confirmer que Query a été appelé ; il ne peut pas confirmer ce que PostgreSQL fera de la requête reçue.
Le cas contesté de GORM
CVE-2019-15562 concerne une ancienne version de GORM et porte la mention « disputed »14. La contestation dit, en substance, que transmettre une entrée non fiable à un emplacement où GORM attend du SQL de confiance constitue une vulnérabilité de l’application, pas de la bibliothèque.
C’est une distinction importante pour attribuer une CVE. Elle aide moins lorsqu’il faut corriger le code. Si une méthode accepte un fragment brut, l’appelant doit garantir que ce fragment ne vient pas de l’utilisateur. La documentation actuelle de GORM le dit explicitement et fournit elle-même des exemples vulnérables.
L’ORM fait confiance au développeur. L’attaquant compte précisément sur le développeur pour transmettre cette confiance un peu trop généreusement.
Tester les corrections plutôt que les apostrophes
Un test utile ne vérifie pas que l’application supprime l’apostrophe. Une apostrophe est un caractère légitime dans un nom. Il vérifie qu’une entrée ne peut pas modifier la structure, le filtre ou le nombre d’instructions exécutées.
Pour chaque point d’entrée concerné, les tests d’intégration devraient couvrir :
' OR '1'='1dans un champ texte ;%et_dans une rechercheLIKE;- un tri inconnu ;
- un nom de colonne sensible mais valide ;
- une chaîne passée comme clé à
First; - une liste
INvide, longue et mal formée ; - une charge utile enregistrée puis relue par une autre fonctionnalité ;
- chaque SGBD réellement pris en charge par l’application.
Le test de second ordre doit traverser les deux opérations : création de la ligne, puis exécution du rapport qui réutilise la valeur. Tester uniquement Create démontre que Create est paramétré. Cela ne dit rien sur le fmt.Sprintf ajouté plus tard dans le paquet d’export.
Les fonctions qui transforment une entrée en enum se prêtent bien au fuzzing natif de Go :
| |
Le fuzzer n’a pas besoin de connaître toutes les charges utiles. La propriété testée est plus forte : quelle que soit la chaîne, la sortie appartient à un ensemble fermé que la couche SQL traduit en constantes.
La stratégie de test complète en Go montre comment séparer tests unitaires, intégration, bout en bout et fuzzing dans le dépôt et dans l’intégration continue. Ici, les tests contre PostgreSQL et SQLite appartiennent clairement à la couche d’intégration.
gosec et govulncheck ne cherchent pas la même chose
gosec examine le code applicatif. govulncheck compare les dépendances et les chemins d’appel à la base de vulnérabilités Go15.
| |
Cette commande est celle de la documentation. govulncheck n’a pas été exécuté dans la campagne de tests de cet article, et aucun résultat mesuré ne lui est donc attribué.
La distinction reste utile :
- un
fmt.Sprintfdans le dépôt relève de gosec et de la revue de code ; - une dépendance affectée par une vulnérabilité publiée relève de
govulncheck; - un
Order(userInput)peut rester invisible si l’analyseur ne modélise pas l’API de GORM ; interpolateParams=trueest une décision de configuration qui peut se trouver hors du fichier analysé.
Une configuration d’intégration continue possible serait :
| |
L’exemple n’a pas été exécuté pour cet article. Dans un dépôt réel, les actions et les outils devraient idéalement être épinglés à des références contrôlées, puis mis à jour par un processus explicite.
Les permissions quand tout le reste a échoué
Les paramètres sont la défense principale. Une liste blanche protège les identifiants. Les permissions limitent les dégâts lorsqu’une erreur subsiste.
Le compte utilisé par l’application ne devrait pas être propriétaire du schéma. Les migrations ont besoin de CREATE, ALTER et parfois DROP ; le serveur HTTP ordinaire, presque jamais. Un service de rapport peut utiliser un compte en lecture seule. Une API limitée peut lire une vue qui n’expose pas les colonnes sensibles.
Cela ne bouche aucun des six trous applicatifs. Cela réduit leur étendue. Une injection sous un compte qui peut lire trois tables reste une vulnérabilité ; elle est néanmoins moins grave qu’une injection sous le propriétaire de toute la base.
Il faut aussi limiter les requêtes : délais via context.Context, taille maximale des listes, profondeur de pagination, plages de dates et absence de messages d’erreur SQL détaillés dans la réponse HTTP. Ces mesures ne réparent pas une injection, mais réduisent les possibilités d’exploration et de déni de service.
La règle qui tient en une phrase
Les valeurs sont liées. La structure est choisie.
Une valeur, qu’il s’agisse d’un texte, d’un nombre, d’une date, d’un UUID ou d’un tableau, passe comme argument. Un élément de structure, comme une colonne, une table, un opérateur ou une direction, vient d’une enum, d’un switch ou d’une liste fermée. Une expression brute est revue comme une requête brute, même lorsqu’elle se cache dans Where. Une donnée relue depuis la base conserve l’origine qu’elle avait avant d’y entrer.
| Cas | Ce qui devient du SQL | Correctif |
|---|---|---|
| 1. Requête brute | Une valeur concaténée | Placeholder et argument séparé |
| 2. Identifiant dynamique | Colonne, table ou direction reçue | Liste fermée vers une constante |
| 3. Second ordre | Une valeur stockée puis recollée | Lier à chaque réutilisation |
| 4. Expression ORM | Expr, Safe ou chaîne formatée | Fragment constant et valeur liée |
5. First avec une chaîne | La clé est interprétée comme condition | Convertir le type ou écrire id = ? |
6. Liste IN | Le slice est joint dans le texte | ANY, sqlx.In, GORM ou bun.In |
| 7. Faille historique du driver | pgx v4 recomposait mal un cas précis | Mettre à jour vers v4.18.2 ou une version ultérieure |
Go aide parce qu’il oblige à convertir les types, facilite les enums et permet à sqlc de générer une couche étroite. Il ne peut pas savoir qu’une chaîne est devenue du SQL. GORM, sqlx, Squirrel, Bun et pgx savent tous lier correctement les valeurs ; ils peuvent aussi recevoir du texte que l’application a déjà décidé de traiter comme du code.
L’ORM ne bouche donc pas les six trous applicatifs. Il fournit les matériaux. Le septième cas rappelle que le driver doit lui aussi rester à jour. Le reste dépend de l’endroit où le programme accepte encore qu’une donnée devienne une requête.
L’article de freeCodeCamp sert de grille de départ : requête brute, identifiant dynamique, injection de second ordre et expression brute cachée dans un appel ORM. L’intérêt ici n’est pas de traduire les exemples JavaScript, mais de vérifier où les mêmes frontières réapparaissent dans les API Go. ↩︎
La documentation Go recommande de passer les arguments séparément et déconseille explicitement
fmt.Sprintfpour assembler une requête. Voir Avoiding SQL injection risk. ↩︎La documentation Query de GORM montre le motif
LIKEdans l’argument du placeholder. Cela protège la structure SQL ; le traitement de%et_reste une décision fonctionnelle de l’application. ↩︎La page Security de GORM contient à la fois l’exemple de
Firstavec une chaîne et la liste des méthodes acceptant du SQL non échappé. Elle précise aussi que la sortie du logger n’est pas entièrement échappée comme la requête exécutée et ne doit pas être copiée aveuglément dans une console SQL. ↩︎ ↩︎pgx.Identifier.Sanitizes’occupe de la représentation syntaxique d’un identifiant. Il ne décide pas si cet identifiant doit être accessible à l’utilisateur. ↩︎La documentation SQL placeholders de Bun distingue
bun.Ident,bun.Safeetbun.In. Ces helpers n’ont pas tous le même contrat :Safesuppose la chaîne déjà fiable, tandis queIdentla traite comme un identifiant. ↩︎ ↩︎ ↩︎PostgreSQL documente
ANYdans Row and Array Comparisons. Cette solution est propre à PostgreSQL ; elle ne constitue pas un remplacement portable deINpour tous les SGBD. ↩︎Le guide Illustrated Guide to SQLX explique l’expansion de
sqlx.Inet l’étapeRebind. OublierRebindavec PostgreSQL laisse des placeholders dans une syntaxe inadaptée au driver. ↩︎Le README de go-sql-driver/mysql explique le but de
interpolateParamset refuse l’option avec plusieurs encodages multioctets. Ce passage décrit le contrat documenté ; l’option n’a pas été testée dans cet article. ↩︎La fiche GO-2024-2605 documente les conditions cumulatives de CVE-2024-27289 et un correctif en v4.18.2 pour
github.com/jackc/pgx/v4. Elle ne fournit pas de base pour déclarer pgx v5 affecté. ↩︎La documentation de sqlc décrit la génération de code Go à partir de requêtes SQL. L’exemple de tri conditionnel de cet article illustre une approche possible, mais n’a pas été compilé dans la campagne de tests. ↩︎
Le fichier RULES.md de gosec répertorie G201, G202 et G701. Une règle existante n’implique pas que tous les appels d’un ORM tiers soient modélisés. ↩︎
La documentation G201/G202 fournit des exemples sur
database/sql. Dans les tests de cet article, G202 a été vérifié avec la concaténation directement placée dansdb.Query, pas avec une variable intermédiaire. ↩︎La fiche CVE-2019-15562 est contestée. Le désaccord porte sur l’attribution du problème à GORM ou au code appelant, pas sur le danger de transmettre une entrée non fiable comme fragment SQL. ↩︎
Le tutoriel officiel Find and fix vulnerable dependencies with govulncheck explique la différence entre vulnérabilité présente dans le graphe de dépendances et chemin d’appel affecté. La commande n’a pas été exécutée pour cet article. ↩︎