jsondiffpatch: prototype pollution via crafted delta in patch()
What's the vulnerability?
jsondiffpatch.patch(object, delta) and the jsonpatch formatter's patch()
apply a delta / JSON patch to a target object by walking the object along
property names and path segments taken from the delta. Through 0.7.5 they do
not reject dangerous segments such as __proto__ or
constructor.prototype.
Because someObject.__proto__ is the live Object.prototype, a crafted
delta whose path targets __proto__ lets an attacker steer patch() out of
the intended object and into the built-in Object.prototype, then assign an
attacker-chosen value onto it. The write affects the prototype globally for
the rest of the Node.js process, so every plain object inherits the injected
property — a prototype pollution integrity issue.
Any code that forwards an attacker-influenced delta into jsondiffpatch.patch()
or the jsonpatch formatter's patch() (for example a delta deserialized from
untrusted JSON request input) is exploitable.
Root Cause Analysis
# RCA Report — CVE-2026-8657
## Summary
`jsondiffpatch` versions before `0.7.6` are vulnerable to prototype pollution in `patch()`, `unpatch()`, `reverse()`, and the JSON Patch formatter's `patch()`. When applying a delta or JSON Patch document, the library traverses the target object using attacker-controlled property names and path segments without rejecting dangerous keys such as `__proto__` or `constructor.prototype`. This allows an attacker to write arbitrary values into `Object.prototype`, affecting every plain object in the running Node.js process.
## Impact
- **Package**: `jsondiffpatch` (npm)
- **Repository**: https://github.com/benjamine/jsondiffpatch
- **Affected versions**: `< 0.7.6`
- **Fixed version**: `0.7.6`
- **CWE**: CWE-1321 (Prototype Pollution)
- **Severity**: High (CVSS 3.1 base 8.2)
Any code that forwards an attacker-influenced delta or JSON Patch into `jsondiffpatch.patch()` or the `jsonpatch` formatter's `patch()` (for example, a delta deserialized from untrusted JSON request input) can be exploited to pollute the global prototype.
## Root Cause
The root cause lies in two places in the vulnerable code (`v0.7.5`):
1. **`packages/jsondiffpatch/src/filters/nested.ts`**: The `patchFilter`, `collectChildrenPatchFilter`, `reverseFilter`, and `collectChildrenReverseFilter` functions iterate over delta keys and patch children using property names taken directly from the delta. They do **not** reject `__proto__` or guard against inherited properties like `constructor.prototype`. For example:
```typescript
for (const name in objectDelta) {
const child = new PatchContext(
(context.left as Record<string, unknown>)[name],
objectDelta[name],
);
context.push(child, name);
}
```
When `name` is `__proto__`, `context.left.__proto__` resolves to `Object.prototype`, causing writes to the global prototype.
2. **`packages/jsondiffpatch/src/formatters/jsonpatch-apply.ts`**: The `parsePathFromRFC6902()` function splits JSON Pointer paths into segments without validating them. When a segment is `__proto__`, the subsequent `add()` or `replace()` operations write into `Object.prototype`.
The fix commit `381c0125efab49f6f0dbc08317d01d55717672af` addresses this by:
- Introducing an `UNSAFE_KEYS` set containing `__proto__` (and `constructor` / `prototype` for the JSON Patch formatter).
- Skipping delta keys that match unsafe names in all patch/unpatch/reverse filters.
- Using `Object.prototype.hasOwnProperty.call(left, name)` to prevent traversal into inherited properties (mitigating `constructor.prototype` attacks).
- Adding validation in `parsePathFromRFC6902()` that throws for unsafe path segments in JSON Patch operations.
## Reproduction Steps
The reproduction script is `repro/reproduction_steps.sh`. It performs the following steps:
1. Installs `jsondiffpatch@0.7.5` (vulnerable) and `jsondiffpatch@0.7.6` (fixed) in separate scratch directories.
2. Runs a shared ES module test against each installation.
3. The test attempts five prototype-pollution vectors:
- `jsondiffpatch.patch()` with a delta containing `__proto__`
- `jsondiffpatch.patch()` with a delta containing `constructor.prototype`
- `jsonpatchFormatter.patch()` with an `add` operation targeting `/__proto__/pp3`
- `jsonpatchFormatter.patch()` with a `replace` operation targeting `/__proto__/pp4`
- `jsondiffpatch.unpatch()` with a delta containing `constructor.prototype`
4. After each attempt, the test checks whether a plain `{}` inherits the injected property. If so, it logs the polluted property names.
5. The script verifies:
- **Vulnerable version (0.7.5)**: prints `VULNERABLE` and exits `0`
- **Fixed version (0.7.6)**: prints `NOT_VULNERABLE` and exits `1`
## Evidence
- **Vulnerable test log**: `logs/test_vuln.log`
```
=== Testing vulnerable version 0.7.5 ===
POLLUTED patch_proto: pp1
POLLUTED patch_constructor: pp2
POLLUTED jsonpatch_add: pp3
POLLUTED jsonpatch_replace: pp4
VULNERABLE
```
- **Fixed test log**: `logs/test_fixed.log`
```
=== Testing fixed version 0.7.6 ===
NOT_VULNERABLE
```
- **Runtime manifest**: `repro/runtime_manifest.json` (written on successful reproduction)
- **Environment**: Node.js v22.22.2, `jsondiffpatch@0.7.5` and `@0.7.6` installed via npm.
## Recommendations / Next Steps
1. **Upgrade immediately** to `jsondiffpatch >= 0.7.6`.
2. **Input validation**: If you must accept deltas or JSON Patches from untrusted sources, validate them independently before passing them to `jsondiffpatch.patch()` or the JSON Patch formatter.
3. **Regression testing**: Include the provided test cases in your CI pipeline to guard against future regressions. The upstream project has added 7 regression tests covering all attack vectors.
4. **Runtime hardening**: Consider using `Object.freeze(Object.prototype)` or libraries like `safe-object-assign` to make prototype pollution harder to exploit in production environments.
## Additional Notes
- **Idempotency**: The reproduction script was run twice consecutively with identical results.
- **Edge cases**: The fix intentionally preserves legitimate use cases (e.g., objects that have their own `constructor` property). The upstream test suite includes a dedicated test (`should still correctly patch objects with a legitimate 'constructor' own property`) confirming this.
- **Scope**: While the reproduction focuses on `patch()` and `jsonpatchFormatter.patch()`, the fix also protects `unpatch()` and `reverse()` because they share the same traversal logic.
Verify with pruva-verify
Run the Pruva CLI to automatically fetch and execute the reproduction script.
pruva-verify REPRO-2026-00135 pruva-verify CVE-2026-8657 curl -fsSL https://pruva.dev/install.sh | sh Or Run Manually
Download the script
curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00135/artifacts/reproduction_steps.sh Make executable
chmod +x reproduction_steps.sh Run the script
./reproduction_steps.sh How Pruva Reproduced This
Watch the AI agent's step-by-step process.
Loading session...
Artifacts
No artifacts available