← Toutes les actualités
security6 min de lecture

Prototype pollution vulnerabilities in AI-generated JavaScript

Par Seaworthy · 25 juin 2026

Dark terminal screen showing JavaScript code with syntax highlighting in a code editor

Photo by Florian Olivo on Unsplash

What prototype pollution actually is

JavaScript objects inherit properties through the prototype chain. Every plain object has Object.prototype sitting at the top of that chain, and any property you set on it shows up on every object in the runtime. Prototype pollution is what happens when an attacker controls the keys in a merge or deep-clone operation and writes to __proto__, constructor, or prototype.

The result is global state corruption. A property you set on Object.prototype is suddenly "present" on every object that doesn't override it. In a server-side Node.js app, that can mean bypassed auth checks, injected configuration values, or in the worst cases, remote code execution through template engines or child_process calls that read from options objects.

The object merge that bites you

Here is the function AI assistants write constantly. It looks correct. It is not:

function merge(target, source) {
  for (const key of Object.keys(source)) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Ask any LLM to "write a deep merge utility in JavaScript" and you will get something close to this. It is clean, readable, and recursively walks nested objects. The problem is that Object.keys(source) returns __proto__ when it is present as an own property on a plain object literal or a parsed JSON payload.

So when a user sends this:

{ "__proto__": { "isAdmin": true } }

The merge call does target["__proto__"]["isAdmin"] = true, which writes directly to Object.prototype. After that, ({}).isAdmin evaluates to true in your process for the lifetime of that Node.js worker.

Why AI-generated code is especially prone to this

This is not a dig at AI tools. It is an observation about how they work. Language models learn from the corpus of code that exists on the internet. Vulnerable merge utilities have been published in tutorials, Stack Overflow answers, and open-source libraries for years, long before prototype pollution was a well-understood class of attack. The model reproduces what it has seen.

There is another factor. AI assistants optimize for readability and correctness under normal inputs. Prototype pollution requires adversarial inputs. A model generating a merge function will test it mentally against merge({a: 1}, {b: 2}), not against merge({}, JSON.parse(userInput)). Security properties are invisible to that kind of reasoning unless the model is explicitly prompted to think about them.

The result is that generated utility code tends to be clean, passes obvious tests, and ships with this class of bug intact.

The fixes

There are a few approaches, each with different tradeoffs.

The fastest fix is to block dangerous keys explicitly:

const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (FORBIDDEN_KEYS.has(key)) continue;
    if (typeof source[key] === 'object' && source[key] !== null) {
      if (!target[key]) target[key] = {};
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

A cleaner approach is to create objects with Object.create(null) when you do not need prototype inheritance. These objects have no __proto__ at all, so pollution has nowhere to land.

The most robust approach for merging untrusted data is to validate the schema before merging, using something like Zod or Ajv. If you know the shape of the data you expect, reject anything that does not match it. You never need to merge __proto__ into anything.

For existing codebases, Object.freeze(Object.prototype) at startup will throw errors if anything tries to mutate the prototype. It is a good canary in development but be careful in production: it changes error behaviour across the board.

Where this shows up in practice

The attack surface is anywhere you merge or clone user-controlled objects without schema validation. Common locations: configuration merging in Express middleware, body parser output fed into lodash-style utilities, query string parsing where nested objects are allowed, and settings overrides in multi-tenant SaaS applications.

The Lodash _.merge function was vulnerable to this until version 4.17.5. The merge-deep package had it. hoek had it. These are not obscure libraries, and the patches were not trivial to write. When you generate a merge utility and skip the test suite, you are not starting from zero: you are reproducing a known vulnerability class that took the ecosystem years to patch properly.

Practical steps

Audit every recursive merge or deep clone in your codebase. Ask yourself: does any key in this operation come from user input, a network response, or a parsed JSON body? If yes, add key filtering or switch to schema validation before you merge.

If you are using a library, check its version against known CVEs for prototype pollution. The npm audit output will catch some of these, but not all: the vulnerability has to be reported and catalogued first.

Do not assume AI-generated utility code is safe to ship without review. The model may have reproduced a pre-patch version of a known pattern. Read it, run it against adversarial inputs, and check it the same way you would check a Stack Overflow snippet.

Seaworthy's static analysis checks for common prototype pollution patterns in JavaScript and TypeScript repositories, including recursive merge utilities that do not filter reserved prototype keys.

Cet article a été généré par IA et résume des sources publiquement disponibles.

Par les créateurs de Seaworthy

Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.

Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.

$ npx seaworthycode