Prototype Pollution acontece quando propriedades controladas pelo usuário alteram protótipos de objetos JavaScript. Chaves como __proto__, constructor e prototype podem contaminar objetos criados posteriormente, modificar decisões de autorização, causar falhas ou preparar outros ataques.
Exemplo vulnerável
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
target[key] ||= {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}Um payload com caminhos perigosos pode alcançar o protótipo global, dependendo da implementação.
Impacto
Exemplos:
isAdminaparece como true em objetos sem campo próprio;- configurações recebem valores inesperados;
- loops e serialização mudam;
- bibliotecas usam opções contaminadas;
- negação de serviço;
- cadeias com template injection ou execução.
Valide propriedades próprias
if (Object.hasOwn(user, 'isAdmin') && user.isAdmin === true) {
// decisão explícita
}Não confie em uma propriedade apenas porque a leitura devolveu valor.
Bloqueie chaves perigosas
const BLOCKED_KEYS = new Set([
'__proto__',
'prototype',
'constructor',
]);
function assertSafeKey(key) {
if (BLOCKED_KEYS.has(key)) {
throw new ValidationError('Chave não permitida');
}
}Aplique recursivamente em estruturas dinâmicas, mas prefira schemas com propriedades conhecidas.
additionalProperties false
JSON Schema pode rejeitar campos inesperados:
{
"type": "object",
"properties": {
"theme": { "enum": ["light", "dark"] }
},
"additionalProperties": false
}Isso reduz objetos arbitrários chegando a funções de merge.
Object.create(null)
const dictionary = Object.create(null);
dictionary[userKey] = value;Objetos sem protótipo são úteis como dicionários. Ainda limite chaves e tamanho.
Map
Para chaves arbitrárias, Map evita semântica de protótipo:
const values = new Map();
values.set(userKey, value);Spread e Object.assign
Spreads modernos possuem comportamento diferente de merges recursivos, mas não transforme isso em garantia universal. O risco depende de setters, destinos e bibliotecas. Use allowlist de campos:
const preferences = {
theme: input.theme,
language: input.language,
};Evite merge profundo de entrada
Não mescle body do usuário diretamente em configuração, entidade ou opções de biblioteca:
// Evite
const config = deepMerge(defaultConfig, req.body);
// Prefira
const config = {
timeoutMs: validateTimeout(req.body.timeoutMs),
mode: validateMode(req.body.mode),
};Bibliotecas
Funções antigas de merge, set por caminho e query parser já tiveram vulnerabilidades. Mantenha dependências atualizadas, use lockfile e revise advisories.
Query strings
Parsers que transformam a[b][c] em objetos profundos precisam de limites e proteção. Configure profundidade, quantidade de parâmetros e chaves proibidas.
JSON.parse
O parse por si só cria propriedades próprias, mas o risco aparece quando o resultado é mesclado ou interpretado por código inseguro. Não considere JSON.parse uma sanitização.
Configurações de segurança
Nunca use defaults herdáveis para autorização:
const allowed = Object.hasOwn(decision, 'allowed') &&
decision.allowed === true;Freeze
Object.freeze protege um objeto específico contra mudanças superficiais, não elimina poluição global nem congela estruturas internas. Pode complementar configurações imutáveis.
Node flags
Versões do Node.js oferecem opções relacionadas ao tratamento de __proto__. Elas podem reduzir superfície, mas não substituem validação e atualização de bibliotecas.
Testes
const payload = JSON.parse('{"__proto__":{"polluted":true}}');
expect(() => updatePreferences(payload)).toThrow();
expect({}.polluted).toBeUndefined();Teste constructor.prototype, caminhos aninhados, query strings e bibliotecas de merge.
Detecção
Secret scanners não detectam esse problema. Use SAST, auditoria de dependências, testes adversariais e revisão de funções que aceitam caminhos ou objetos dinâmicos.
Observabilidade
Registre rejeições por chave categorizada, sem copiar payload completo. Um pico pode indicar scanner automatizado.
Erros comuns
- merge profundo de req.body;
- confiar em propriedades herdadas;
- permitir chaves arbitrárias;
- parser de query sem limite;
- dependência desatualizada;
- considerar JSON.parse seguro por si só;
- usar objeto comum como dicionário;
- tentar corrigir apenas removendo __proto__ no nível superior.
Fluxo recomendado
- valide com schema;
- use allowlist de campos;
- bloqueie chaves perigosas recursivamente;
- evite merge profundo;
- use Map ou objeto sem protótipo;
- verifique propriedades próprias;
- atualize dependências;
- teste payloads adversariais;
- monitore rejeições.
Combine a proteção com JSON Schema, Tratamento de Erros, Rate Limiting e Audit Logs.
Consulte a referência da OWASP sobre Prototype Pollution e a documentação de protótipos da MDN.



