O que é injeção de __proto__
Resumo: `JSON.parse` trata `__proto__` como uma chave comum; o dano acontece em uma mesclagem recursiva que grava em `Object.prototype`, então todo objeto herda propriedades do atacante.

Resumo da ópera: bloqueie __proto__, constructor e prototype antes de qualquer merge, mescle entrada não confiável em Object.create(null) e adicione uma asserção de protótipo ao CI; atualizar bibliotecas sozinho não basta.
Em uma frase: uma chave não confiável chamada __proto__ encontra um merge recursivo, e o merge grava no protótipo.
Uma reprodução
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
Nenhum objeto de negócio foi tocado. Object.prototype foi, e todo objeto agora carrega isAdmin.
Por que a culpa não é do JSON.parse
const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data); // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype; // true, intocado
__proto__ chega como uma propriedade própria comum. O perigo está inteiramente no código que depois mescla, copia profundamente ou define por caminho — _.merge, alguns padrões de Object.assign e incontáveis helpers escritos à mão do tipo set(obj, path, value).
Impacto
- Bypass de autorização:
if (user.isAdmin)se torna incondicionalmente verdadeiro - Corrupção de dados: campos inesperados são serializados em registros armazenados
- Negação de serviço: objetos grandes anexados ao protótipo crescem a memória permanentemente
- Escalonamento: combinado com engines de template ou executores de comando, isso já levou a execução remota de código
Quatro defesas
Filtre as chaves.
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
Bloquear apenas __proto__ é insuficiente: constructor.prototype chega ao mesmo lugar.
Mescle em um objeto de protótipo nulo.
const safeTarget = Object.create(null);
Sem cadeia de protótipos, __proto__ é apenas uma chave de string.
Use Object.hasOwn em vez de in. O in consulta a cadeia de protótipos.
Mantenha as dependências atualizadas. lodash, deepmerge e json-patch todos já lançaram correções para essa classe de bug. Acompanhe o npm audit.
Verifique se continua corrigido
Passe um payload contendo __proto__ e constructor pelo seu merge e depois assegure:
console.log({}.polluted); // undefined
console.log(Object.prototype.polluted); // undefined
Uma asserção no CI, custo quase zero, pega regressões de refatorações posteriores.
Perguntas frequentes
Que chaves um merge deve filtrar?
Bloqueie __proto__, constructor e prototype juntos. Filtrar apenas __proto__ é insuficiente, porque constructor.prototype chega ao mesmo protótipo.
Por que Object.create(null) impede a gravação?
Um objeto de protótipo nulo não tem cadeia de protótipos, então __proto__ permanece uma chave de string comum. Ele funciona como contêiner de dados, mas não substitui o filtro de chaves nem a atualização de dependências.
Como testo prototype pollution no CI?
Envie um payload contendo __proto__ e constructor pelo seu merge e depois assegure que {}.polluted e Object.prototype.polluted sejam undefined. Uma asserção não custa quase nada e pega regressões de refatorações posteriores.