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.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00135
or pruva-verify CVE-2026-8657
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00135/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available