# Why AI Editors Keep Generating Prototype Pollution Vulnerabilities

> Source: <https://sourcefeed.dev/a/why-ai-editors-keep-generating-prototype-pollution-vulnerabilities>
> Published: 2026-07-10 14:04:00+00:00

[Security](https://sourcefeed.dev/c/security)Article

# Why AI Editors Keep Generating Prototype Pollution Vulnerabilities

Recursive merge helpers generated by AI tools frequently lack basic security guards, exposing JavaScript applications to prototype pollution.

[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)

We ask an AI assistant like [Cursor](https://www.cursor.com) to write a quick helper function to deep-merge two configuration objects. The model spits out an elegant, eight-line recursive function. It passes your unit tests. It handles nested objects perfectly. But it also introduces a critical security vulnerability (CWE-1321) that can compromise your entire Node.js process or client-side application.

This is not an isolated glitch. AI code assistants repeatedly generate vulnerable recursive merge patterns. The bug is silent, invisible during normal execution, and highly exploitable.

## The Anatomy of the Generated Bug

Let's look at the classic recursive merge function that LLMs love to write:

``` js
function merge(target, source) {
  for (const key in source) {
    if (source[key] && typeof source[key] === 'object') {
      target[key] = merge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}
```

On the surface, this looks like standard JavaScript. But the vulnerability triggers when the `source`

object contains untrusted user input, such as a parsed JSON request body.

Consider this payload:

``` js
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, payload);
```

When `JSON.parse()`

runs, it treats `__proto__`

as an ordinary string key, returning an object that has an own property named `__proto__`

. When our generated `merge`

function processes this key, it evaluates `target["__proto__"]`

. In JavaScript, `__proto__`

acts as a getter and setter for the object's prototype.

Instead of setting a property on the local target object, the assignment recurses and writes `isAdmin: true`

directly onto `Object.prototype`

. Because almost every object in JavaScript inherits from `Object.prototype`

through the prototype chain, every single object in the application now has `isAdmin`

set to `true`

.

```
console.log(({}).isAdmin); // true
```

If your application subsequently checks `if (user.isAdmin)`

for authorization, an unauthenticated attacker who sent that JSON payload now has administrative privileges.

## Why LLMs Keep Recreating This Vulnerability

Why does this pattern persist in AI-generated code? The answer lies in the training corpus.

For over a decade, developers have shared simple recursive merge snippets on StackOverflow, blogs, and GitHub. Most of these historical snippets do not guard special keys. Because the missing check never breaks the happy-path execution of a test suite, these insecure implementations remained popular and highly ranked.

LLMs reproduce the statistical shape of their training data. They generate code that looks correct and satisfies basic functional requirements. Since prototype pollution only manifests under malicious input, standard test suites generated by developers (or the AI itself) do not catch it.

## The Multi-Key Trap: Why Partial Fixes Fail

Fixing prototype pollution is not as simple as blocking `__proto__`

. This is a common trap that even popular open-source libraries have fallen into.

For example, the `merge`

package (vulnerabilities tracked under SNYK-JS-MERGE-1040469) attempted to prevent prototype pollution by checking for `__proto__`

, but failed to block `constructor`

or `prototype`

. An attacker can bypass a simple `__proto__`

check by targeting the constructor property:

``` js
// Bypassing __proto__ filters via constructor.prototype
const payload = JSON.parse('{"constructor": {"prototype": {"isAdmin": true}}}');
```

This is why robust sanitization must block all three dangerous keys: `__proto__`

, `constructor`

, and `prototype`

.

This exact issue was debated and resolved in the popular utility library `dset`

(PR #34 on GitHub). The maintainers added explicit checks to break the loop if any of these keys are encountered:

```
if (k === '__proto__' || k === 'constructor' || k === 'prototype') break;
```

To secure the AI-generated helper, we must apply the same logic:

``` js
function merge(target, source) {
  for (const key in source) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue;
    }
    if (source[key] && typeof source[key] === 'object') {
      target[key] = merge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}
```

## Developer Workflow and Mitigations

How should developers handle this risk in production workflows?

First, avoid hand-rolling recursive merge functions. Standard libraries like Lodash have gone through years of security hardening to address these edge cases. If you need a deep merge, use a well-maintained library rather than asking an AI to generate a custom helper.

Second, if you must merge user-controlled data into plain objects, consider using objects without prototypes. You can instantiate an object with `Object.create(null)`

. This breaks the prototype chain entirely, meaning there is no `__proto__`

or `constructor`

property to pollute.

``` js
const safeObject = Object.create(null);
// safeObject has no prototype chain, making it immune to prototype pollution
```

Alternatively, use a `Map`

instead of a plain JavaScript object for storing dynamic user-defined key-value pairs.

To catch these issues before they reach production, integrate static analysis tools into your CI/CD pipeline. A basic pre-commit hook running Semgrep or [CodeQL](https://codeql.github.com) will flag unsafe recursive merge patterns immediately. You can also use security scanning tools from [Snyk](https://snyk.io) to monitor your dependencies for known prototype pollution vulnerabilities.

AI code assistants are powerful accelerators, but they are historical mirrors, not security experts. When you ask an editor to generate utility functions, remember that it is pulling from a legacy of code written before modern security threats were fully understood. Guard your keys, validate your JSON schemas, and never trust a generated recursive function without a thorough manual review.

## Sources & further reading

-
[Why Cursor Keeps Writing Prototype Pollution Into Your Merge Code](https://dev.to/c_k_fb750e731394/why-cursor-keeps-writing-prototype-pollution-into-your-merge-code-291a)— dev.to -
[Prototype Pollution in merge | Snyk](https://security.snyk.io/vuln/SNYK-JS-MERGE-1040469)— security.snyk.io -
[What is prototype pollution? | Web Security Academy](https://portswigger.net/web-security/prototype-pollution)— portswigger.net -
[How to prevent prototype pollution vulnerabilities in JavaScript | Snyk](https://snyk.io/articles/prevent-prototype-pollution-vulnerabilities-javascript/)— snyk.io -
[fix: possible prototype pollution within merge by n1ru4l · Pull Request #34 · lukeed/dset](https://github.com/lukeed/dset/pull/34)— github.com

[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)· Security & Cloud Editor

Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.

## Discussion 0

No comments yet

Be the first to weigh in.
