What is Prototype Pollution?
Every object in JavaScript inherits from Object.prototype. If an attacker can write to that prototype — say, by passing __proto__ as a key in a JSON body — every object in your app suddenly has that new property. An isAdmin check that defaults to undefined? Now it's true.
This isn't hypothetical. Prototype pollution has been found in widely-used libraries like Lodash, jQuery, and Express. The attack works because JavaScript's object model trusts prototype chain lookups by default — there's no built-in guard against writing to __proto__ or constructor.prototype.
What makes this especially dangerous in AI-generated code is that deep merge and config-parsing utilities are among the first things AI writes. These functions recursively assign properties from user input to objects without filtering dangerous keys — exactly the pattern that enables pollution.
How does Prototype Pollution work?
Prototype pollution needs two things: a code path that recursively merges or assigns properties from user input onto an object, and no filtering of special keys like __proto__, constructor, or prototype.
Here's a deep merge function that AI commonly generates:
// lib/utils.ts
function deepMerge(target: any, source: any) {
for (const key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// API route
app.post('/api/settings', (req, res) => {
const settings = deepMerge({}, req.body);
// Attacker sends: { "__proto__": { "isAdmin": true } }
// Now EVERY object has isAdmin === true
res.json(settings);
});import { z } from 'zod';
const settingsSchema = z.object({
theme: z.enum(['light', 'dark']).optional(),
language: z.string().max(5).optional(),
notifications: z.boolean().optional(),
});
app.post('/api/settings', (req, res) => {
// Schema validation strips unexpected keys entirely
const parsed = settingsSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Invalid settings' });
}
// Object.create(null) has no prototype — can't be polluted
const settings = Object.assign(Object.create(null), parsed.data);
res.json(settings);
});Why do AI tools generate Prototype Pollution vulnerabilities?
AI code generators love writing utility functions from scratch. Deep merge, config parsers, and object spread helpers are among the most commonly generated patterns — and they almost never include prototype pollution guards.
- Utility functions are written inline. Instead of using a vetted library, AI generates its own deepMerge or extend function. These hand-rolled versions rarely filter __proto__ or constructor keys.
- Training data includes vulnerable patterns. Older Stack Overflow answers and tutorials show recursive merge without any mention of prototype safety. The model reproduces what it learned.
- The vulnerability is invisible in testing. A polluted prototype doesn't crash anything immediately. Unit tests pass. The app works fine — until an attacker sends a crafted payload.
Prototype pollution often acts as a stepping stone. On its own, it might seem low-impact. But combined with other patterns — like an <code>isAdmin</code> check that reads from the object — it becomes a full privilege escalation chain.
Common Prototype Pollution patterns
Deep merge from user input
Recursively merging req.body into a config or settings object without filtering keys.
Query string parsing
Libraries like qs can parse nested keys: ?__proto__[isAdmin]=true becomes a polluted prototype.
Template engine gadgets
Polluting prototype properties that template engines like Pug or Handlebars read during rendering, leading to XSS or RCE.
Config file merging
Merging user-uploaded JSON or YAML config files into application defaults without schema validation.
How Flowpatrol detects Prototype Pollution
Flowpatrol tests for prototype pollution by sending crafted payloads to every endpoint that accepts JSON input:
- 1Identifies merge endpoints by mapping API routes that accept nested JSON objects — settings, profiles, configs.
- 2Sends __proto__ payloads with known canary properties and checks whether subsequent responses reflect the polluted values.
- 3Tests constructor.prototype paths because some frameworks filter __proto__ but miss the constructor.prototype vector.
- 4Checks for downstream impact — if a polluted property affects auth checks, template rendering, or other security-sensitive logic.
Most scanners test for prototype pollution in known library versions. Flowpatrol tests your actual code paths — because AI-generated merge functions won't show up in any CVE database.
Related terms
Check your app for Prototype Pollution.
Flowpatrol sends real payloads to your merge and config endpoints. Five minutes. One URL.
Try it free