Guias de Segurança
Prototype pollution no Node.js: o que o runtime não cobre e como se defender no seu próprio código
Uma chave especial nos dados recebidos, levada por um merge recursivo, faz propriedades que você nunca definiu aparecerem em objetos sem relação. O Node.js diz que isso não é uma vulnerabilidade do núcleo, então a defesa é responsabilidade sua.
Você recebe dados de fora e, depois, propriedades que você nunca definiu começam a aparecer em objetos sem relação. Ou um método padrão como hasOwnProperty de repente "não é uma função" e o app cai. As duas coisas são sintomas de prototype pollution (poluição de protótipo).
O que o Node.js cobre e o que fica com você
O Node.js diz claramente que esta vulnerabilidade não é dele
O guia oficial de segurança do Node.js é explícito: "Segundo o modelo de ameaças do Node.js, a prototype pollution que depende de um atacante controlar a entrada do usuário não é considerada uma vulnerabilidade do núcleo do Node.js, porque o Node.js confia nas entradas fornecidas pelo código da aplicação."
E logo em seguida: "Mesmo assim, a prototype pollution é uma classe séria de vulnerabilidades para aplicações Node.js e bibliotecas de terceiros, e você deve implementar defesas no nível da aplicação e das dependências."
Isso não é uma contradição; é a declaração de onde fica a responsabilidade. O runtime se coloca do lado que não garante que a entrada esteja bem formada. Ou seja, "esperar um CVE, esperar uma atualização" não funciona aqui. É mais um caso de algo com que este site esbarra o tempo todo: a parte que você supunha que alguém cuidava não tem ninguém responsável por ela.
O que dá errado (dois resultados)
Entrada externa (JSON, query, formulário)
traz uma chave especial
↓ merge recursivo / cópia profunda / montagem de configuração
O modelo compartilhado (protótipo) é reescrito
↓ e com ele tudo o que herda dele
1. Aparecem valores que você nunca definiu
flags de permissão e verificações de opções se desviam
2. Métodos nativos desaparecem
o app cai num ponto sem relação (DoS)
- 1. Injeção de propriedades
- Um valor "que não poderia ter sido definido" passa a ser lido. Em qualquer lugar onde um objeto de opções ou uma flag de permissão é decidido por uma simples verificação de existência, o valor injetado é simplesmente aceito. Se chegar a uma decisão de autorização, vira um problema de privilégios; se chegar à montagem de templates ou comandos, pode ficar bem pior
- 2. Negação de serviço
- Quebre o próprio modelo e um método nativo como
hasOwnPropertygera um erro "is not a function". A documentação apresenta isso como um possível DoS. O ponto que vale guardar: não se trata só de escalada de privilégios - Até onde chega
- Os exemplos citados são o próprio Node.js (CVE-2022-21824) e uma biblioteca de terceiros (Lodash, CVE-2018-3721). Não escrever um merge recursivo você mesmo não muda nada se uma dependência escreveu um
As mitigações que a documentação lista (sete)
O Node.js as enumera. Fica mais fácil aplicá-las agrupando pelo que fazem: barrar na entrada, tornar a estrutura impossível de poluir e mudar a forma de ler.
Barrar na entrada
・Evitar merges recursivos inseguros (a documentação cita o CVE-2018-16487)
・Implementar validações com JSON Schema para requisições externas e não confiáveis
Nunca deixar entrar uma chave inesperada é o caminho mais seguro. Reavalie designs que aceitam um objeto profundo inteiro e o mesclam ao estado existente.
Mudar a estrutura e a leitura
・Object.create(null) para criar objetos sem protótipo (ideal para dicionários)
・Object.freeze(MyObject.prototype) para congelar o protótipo
・A flag --disable-proto do Node para desativar Object.prototype.proto
・Object.hasOwn(obj, key) para verificar se a propriedade é do próprio objeto
・Evitar usar métodos de Object.prototype
A mudança de maior efeito é a forma de testar a existência
Na prática, o ganho mais amplo é passar a usar Object.hasOwn(obj, key). Uma verificação de existência simples — essa propriedade existe? — também responde sim para valores herdados do modelo, que é exatamente como um valor injetado é captado. Confirmar que a propriedade é do próprio objeto evita sozinho a maior parte do resultado 1.
Pelo mesmo motivo, evite chamar obj.hasOwnProperty(key) a partir do próprio objeto — esse método é uma das coisas que a poluição pode remover (resultado 2).
O que verificar no seu próprio código
Você aceita entrada externa como um objeto profundo?
Corpos de requisição, query strings, formulários, payloads de webhook — encontre todos os lugares que recebem uma estrutura aninhada inteira e a mesclam ao estado existente. É só por ali que a entrada externa entra. Decidir explicitamente quais chaves você aceita, por meio de um schema, é a correção mais curta.
Mude a forma de criar objetos do tipo dicionário
Tudo o que é usado como "um contêiner cujas chaves são decididas em tempo de execução" (um mapa indexado por ID, um conjunto de configurações) deve ser criado com Object.create(null). Sem modelo, não há nada para herdar. Resolva na estrutura o que de outro modo você teria de resolver na validação.
Padronize as verificações de existência com Object.hasOwn
Procure ramificações que dependem de uma propriedade existir e converta-as. Verificações de permissão, flags e opções vêm primeiro — elas se ligam diretamente à forma como a autorização é implementada.
Coloque uma máquina para vigiar as dependências
Não escrever um merge recursivo você mesmo não muda nada se uma dependência escreveu um — e é por isso que os exemplos citados incluem uma biblioteca de terceiros. Vulnerabilidades conhecidas em dependências não podem ser acompanhadas à mão, então coloque-as sob monitoramento automático como em primeiros passos com o osv-scanner. O risco mais amplo de uma dependência envenenada está em como se defender do comprometimento da cadeia de suprimentos do npm.
Olhe da mesma forma para a entrada do seu framework de servidor
Todo framework Node tem um caminho que desserializa o corpo da requisição. Quando verificamos os dados do NVD, as vulnerabilidades do Next.js se concentravam em responsabilidades do lado do servidor — cache, middleware, Server Actions (Next.js e React têm muitas vulnerabilidades?). Comece abandonando a suposição de que "é uma biblioteca de front-end, então isso não se aplica".
A visão deste site: onde a responsabilidade é declarada explicitamente, verifique você mesmo
O mais útil na prática neste artigo não é a lista de mitigações — é a frase dizendo que isso não é considerado uma vulnerabilidade do núcleo do Node.js. Onde a responsabilidade é declarada com tanta clareza, ninguém cuida do que fica fora dela: nenhum CVE vai chegar e nenhuma atualização vai corrigir.
Tratamos isso como o mesmo tipo de problema de um certificado que não protege você de uma tomada de controle (subdomain takeover) e de um bloqueio que não apaga a política existente por baixo dele (buckets públicos). Quem é de fato responsável pelo lugar que você supunha estar coberto? Responder a isso uma vez costuma reordenar bastante as suas prioridades.
Fontes (primárias)
- Node.js, "Security Best Practices" (seção Prototype Pollution Attacks / CWE-1321) — nodejs.org (a posição do modelo de ameaças, os dois resultados, os CVEs citados e as sete mitigações vêm todos deste documento; consultado em 5 de setembro de 2026)
Leia a seguir
- O mesmo formato: desserialização insegura (dados externos decidindo o tipo)
- Dependências: primeiros passos com o osv-scanner / como se defender do comprometimento da cadeia de suprimentos do npm
- O panorama: Next.js e React têm muitas vulnerabilidades? (o que aparece do lado do servidor)
- Design: autenticação vs. autorização (para que um valor injetado nunca chegue a uma decisão de permissão)
FAQ
QPrototype pollution é um bug do Node.js? Uma atualização vai corrigir?
Nenhuma atualização vai chegar. O guia oficial de segurança do Node.js declara que, segundo o modelo de ameaças do Node.js, a prototype pollution que depende de um atacante controlar a entrada do usuário não é considerada uma vulnerabilidade do núcleo do Node.js, porque o Node.js confia nas entradas fornecidas pelo código da aplicação. E continua: mesmo assim, a prototype pollution é uma classe séria de vulnerabilidades para aplicações Node.js e bibliotecas de terceiros, e você deve implementar defesas no nível da aplicação e das dependências. Por design, isso é responsabilidade sua.
QO que de fato dá errado?
Dois resultados. Primeiro, propriedades que você nunca definiu começam a aparecer em objetos de todo o processo — se uma flag de permissão ou uma opção é decidida por uma simples verificação de existência, esse valor injetado é aceito, e a autorização ou a lógica de negócio se desviam. Segundo, negação de serviço: quebre o protótipo nativo e um método padrão como hasOwnProperty gera um erro 'is not a function', derrubando código que não tem nada a ver com a entrada. O segundo resultado mostra que não se trata só de escalada de privilégios.
QOnde eu corrijo?
Na entrada e nas estruturas de dados. Na entrada, aplique validação com JSON Schema às requisições externas e não confiáveis para que chaves inesperadas nunca entrem. Nas estruturas, pare de usar merges recursivos inseguros e crie objetos do tipo dicionário com Object.create(null) para que não tenham protótipo algum. Na leitura, use Object.hasOwn(obj, key) para confirmar que a propriedade é do próprio objeto, e evite depender de métodos de Object.prototype. Na operação, há ainda Object.freeze e a flag --disable-proto do Node.
QNunca escrevi um merge recursivo — estou seguro?
Não. Os exemplos citados pela documentação são tanto o próprio Node.js (CVE-2022-21824) quanto uma biblioteca de terceiros (Lodash, CVE-2018-3721). Mesclagem de configurações, parsing de query string e desserialização de formulários são todos lugares onde bibliotecas muito usadas montam objetos profundos por você. Por isso a defesa não se fecha dentro do seu código; ela vem junto com o monitoramento automático das suas dependências.