Ce qui se passe
QueryException interpole les valeurs liées dans son message. Un insert qui
échoue place donc toutes les valeurs dans getMessage(), et ce message part
partout où vont les exceptions : la table failed_jobs, les fichiers de journal,
les traces de votre outil de supervision. Adresses e-mail, noms, tout ce qui
était dans la requête.
Ce n'est pas une faille, c'est le comportement par défaut. Il est resté invisible parce que personne ne lit un message d'exception en se demandant ce qu'il contient.
Ce qui change
Une clé de configuration par connexion coupe l'interpolation et laisse les
? en place :
'mysql' => [
'driver' => 'mysql',
// ...
'mask_bindings_in_exception_messages' => env('DB_MASK_BINDINGS', false),
],// par défaut
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (foo@example.com)
// masqué
SQLSTATE[23000]: ... SQL: insert into `users` (`email`) values (?)Deux points pratiques. La clé est livrée dans le config/database.php du
framework : une application qui n'a jamais publié ce fichier l'active avec
DB_MASK_BINDINGS=true seul. Et seul le message change — getBindings()
retourne toujours les valeurs réelles, donc le débogage n'est pas dégradé.
whereBinary(), et le piège de la casse
Sous MySQL, une collation par défaut est insensible à la casse : chercher un nom
égal à John retourne aussi john. Quatre méthodes apportent la comparaison
octet à octet :
DB::table('queues')->whereBinary('name', $queueName)->first();
// select * from `queues` where `name` = binary ?
DB::table('queues')->whereNotBinary('name', $queueName)->get();Avec orWhereBinary() et orWhereNotBinary(). MariaDB hérite de la grammaire
MySQL, donc ça fonctionne aussi. Postgres, SQLite et SQL Server lèvent une
RuntimeException — le même parti pris que whereLike() sur les moteurs déjà
sensibles à la casse.
Deux correctifs à ne pas manquer
Request::merge(['*' => 226]) effaçait tout le tableau d'entrée au lieu d'y
ajouter une clé *, parce que le réducteur passait les clés par data_set(),
qui traite * comme un joker. Uri::withQuery() avait le même défaut un étage
plus haut : ?role=user&tenant=10 devenait ?role=admin&tenant=admin. Les deux
passent désormais par Arr::set().
MaintenanceModeBypassCookie::isValid() vérifiait que le mac du cookie était
défini, pas qu'il était une chaîne. Un cookie laravel_maintenance portant un
tableau le transmettait à hash_equals() et transformait un 503 attendu en 500
non authentifié. Le isset() est devenu is_string().
Source : Laravel News, Query Binding Masking and whereBinary() in Laravel 13.27 · masquage : PR #61326