Qu'est-ce que l'injection __proto__
L’essentiel : JSON.parse traite __proto__ comme une clé ordinaire ; les dégâts surviennent dans une fusion récursive qui écrit dans Object.prototype, donc chaque objet hérite des propriétés de l'attaquant.

L'essentiel : bloquez __proto__, constructor et prototype avant toute fusion, fusionnez les entrées non fiables dans Object.create(null), et ajoutez une assertion de prototype en CI ; patcher les bibliothèques seul ne suffit pas.
En une phrase : une clé non fiable nommée __proto__ rencontre une fusion récursive, et la fusion écrit dans le prototype.
Une reproduction
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = target[key] || {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin); // true
Aucun objet métier n'a été touché. Object.prototype l'a été, et chaque objet porte maintenant isAdmin.
Pourquoi JSON.parse n'est pas en cause
const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data); // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype; // true, untouched
__proto__ arrive comme une propriété propre ordinaire. Le danger est entièrement dans le code qui fusionne, copie profondément ou définit par chemin ensuite — _.merge, certains motifs Object.assign, et d'innombrables helpers set(obj, path, value) écrits à la main.
Impact
- Contournement d'autorisation :
if (user.isAdmin)devient inconditionnellement vrai - Corruption de données : des champs inattendus se sérialisent dans les enregistrements stockés
- Déni de service : de grands objets attachés au prototype font croître la mémoire définitivement
- Escalade : combiné à des moteurs de gabarit ou des exécuteurs de commandes, cela a mené à une exécution de code à distance
Quatre défenses
Filtrez les clés.
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
Bloquer seulement __proto__ est insuffisant : constructor.prototype atteint le même endroit.
Fusionnez dans un objet à prototype nul.
const safeTarget = Object.create(null);
Sans chaîne de prototypes, __proto__ n'est qu'une clé de chaîne.
Utilisez Object.hasOwn au lieu de in. in consulte la chaîne de prototypes.
Gardez les dépendances patchées. lodash, deepmerge et json-patch ont tous livré des correctifs pour cette classe de bug. Surveillez npm audit.
Vérifier que ça reste corrigé
Passez une charge utile contenant __proto__ et constructor dans votre fusion, puis assertissez :
console.log({}.polluted); // undefined
console.log(Object.prototype.polluted); // undefined
Une assertion en CI, coût quasi nul, attrape les régressions des refactorings ultérieurs.
FAQ
Quelles clés une fusion doit-elle filtrer ?
Bloquez __proto__, constructor et prototype ensemble. Filtrer seulement __proto__ est insuffisant, car constructor.prototype atteint le même prototype.
Pourquoi Object.create(null) arrête-t-il l'écriture ?
Un objet à prototype nul n'a pas de chaîne de prototypes, donc __proto__ reste une clé de chaîne ordinaire. Il fonctionne comme conteneur de données, mais il ne remplace pas le filtrage des clés ni les mises à jour de dépendances.
Comment tester la pollution de prototype en CI ?
Passez une charge utile contenant __proto__ et constructor dans votre fusion, puis assertissez que {}.polluted et Object.prototype.polluted sont undefined. Une assertion ne coûte presque rien et attrape les régressions des refactorings ultérieurs.