# REPRO-2026-00135: jsondiffpatch: prototype pollution via crafted delta in patch() ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00135 CVE: CVE-2026-8657 ## Package Name: jsondiffpatch Ecosystem: npm Affected: < 0.7.6 Fixed: 0.7.6 ## Root Cause # 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)[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. ## Reproduction Details Reproduced: 2026-05-22T09:33:23.097Z Duration: 1406 seconds Tool calls: 144 Turns: 128 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00135 pruva-verify CVE-2026-8657 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00135&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00135/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-8657 ## Artifacts - repro/rca_report.md (analysis, 5571 bytes) - repro/reproduction_steps.sh (reproduction_script, 4787 bytes) - vuln_variant/rca_report.md (analysis, 6337 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 6971 bytes) - bundle/context.json (other, 3284 bytes) - bundle/metadata.json (other, 632 bytes) - bundle/ticket.md (ticket, 3780 bytes) - repro/runtime_manifest.json (other, 516 bytes) - repro/validation_verdict.json (other, 1534 bytes) - vuln_variant/patch_analysis.md (documentation, 6403 bytes) - vuln_variant/variant_manifest.json (other, 3361 bytes) - vuln_variant/validation_verdict.json (other, 1443 bytes) - vuln_variant/test_variants.mjs (other, 2733 bytes) - logs/npm_install_vuln.log (log, 0 bytes) - logs/fixed_variant_test.log (log, 15 bytes) - logs/npm_install_fixed_variant.log (log, 0 bytes) - logs/vuln_variant_test.log (log, 205 bytes) - logs/npm_install_vuln_variant.log (log, 0 bytes) - logs/npm_install_fixed.log (log, 0 bytes) - logs/test_fixed.log (log, 51 bytes) - logs/test_vuln.log (log, 170 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00135 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00135/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00135 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev