Admin — bulk_create modifiait des lignes existantes depuis une création : pour une ressource déclarant bulk_create, la fonction de création générée mettait à jour une ligne dont la valeur existait déjà. Sur une création, la porte ne vérifie que le droit de créer : un membre du staff sans droit de modification — ou sans propriété de la ligne, ou sur le compte d'un superutilisateur — pouvait donc modifier des lignes existantes. Une création n'insère plus que : les valeurs existantes sont ignorées sans tenir compte de la casse (Gluten = gluten, nouvel utilitaire text_eq_ci), les doublons du même envoi aussi, et un envoi dont toutes les valeurs existent est refusé (admin.bulk_create.all_exist, 9 langues). Régénérez l'admin (runique start) : le correctif est dans le code généré.
Admin — comptes superutilisateur : un membre du staff ayant le droit de modifier users, sans être superutilisateur, pouvait modifier le compte d'un superutilisateur — y mettre son propre email, puis demander une réinitialisation du mot de passe et se connecter à sa place (ou simplement lire le lien de réinitialisation à l'écran sans service d'email). Modifier, supprimer et réinitialiser le compte d'un superutilisateur, seul ou par une action groupée, exigent désormais un superutilisateur ; la consultation reste ouverte. Le refus est affiché (admin.access.superuser_only, 9 langues), et une erreur de lecture refuse aussi.
Open redirect — deux autres façons de contourner la protection : une tabulation qui cache une URL relative au protocole (/\t/evil.com, que les navigateurs lisent comme //evil.com), et un Location contenant un octet non ASCII, que la protection ne pouvait pas lire comme du texte et laissait passer sans vérification. La cible est désormais résolue par l'analyseur d'URL WHATWG suivi par les navigateurs (crate url), et un en-tête illisible est lu avec remplacement au lieu d'être ignoré. Un chemin relatif sans / initial (page2) est maintenant reconnu comme restant sur le site ; un schéma autre que http/https est refusé. L'en-tête Refresh (0; url=//evil.com), que les navigateurs suivent quel que soit le statut, n'était pas vérifié du tout : il passe désormais par le même contrôle, quelle que soit son écriture (URL=, guillemets, ,, sans mot-clé).
Correctif
Admin — modification avec un formulaire partiel : la mise à jour générée remettait à false, "" ou NULL toutes les colonnes que le formulaire d'édition n'affiche pas. Elle les laisse désormais intactes (admin_partial_update) ; les champs du formulaire, cases non cochées comprises, sont écrits comme avant. À récupérer en régénérant l'admin.
Service d'email : un sujet contenant un retour à la ligne est refusé (injection d'en-têtes), et le HTML de secours des emails de compte et de réinitialisation échappe le nom d'utilisateur et le lien ; l'échec d'un template d'email est désormais tracé au lieu d'être remplacé en silence.
Champs binaires (binary, var_binary, blob) : la limite de taille de la colonne était sautée quand l'upload ne pouvait pas être reconnu ou mesuré à la validation, alors que finalize pouvait encore le lire et l'enregistrer en base. Un tel upload est désormais refusé (forms.file_unreadable, 9 langues) et la raison est tracée. Les champs fichier (FileField) sautaient leur limite de taille de la même façon quand la taille d'un upload ne pouvait pas être lue ; ils le refusent désormais aussi, tandis qu'un chemin déjà enregistré (plats/photo.png) est toujours conservé tel quel.
runique create-superuser ne lisait que DATABASE_URL et plantait sans elle, alors que l'application accepte aussi DB_ENGINE + DB_USER / DB_PASSWORD / DB_HOST / DB_PORT / DB_NAME : un projet configuré ainsi démarrait, mais son superutilisateur ne pouvait pas être créé. L'assistant lit désormais les mêmes clés que l'application, en reconstruit l'URL, et signale une clé manquante par une erreur.