# REPRO-2026-00134: lodash: prototype pollution in _.unset/_.omit deletes global prototype methods ## Summary Status: published Severity: medium Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00134 GHSA: GHSA-xxjr-mmjv-4gpg CVE: CVE-2025-13465 ## Package Name: lodash Ecosystem: npm Affected: >= 4.0.0, <= 4.17.22 Fixed: 4.17.23 ## Root Cause # RCA Report: CVE-2025-13465 ## Summary CVE-2025-13465 is a prototype-pollution-by-deletion vulnerability in lodash's `_.unset` and `_.omit` functions. When a user-controlled string path containing `__proto__` is passed to these functions, lodash traverses into `Object.prototype` and deletes the targeted property globally. This affects every object in the Node.js process, causing denial-of-service or integrity degradation. The issue is fixed in lodash 4.17.23 by adding a guard in `baseUnset` that rejects dangerous string path segments (`__proto__`, `constructor.prototype`). ## Impact - **Package**: `lodash` (npm) - **Affected versions**: 4.0.0 through 4.17.22 (inclusive) - **Fixed version**: 4.17.23 - **Risk level**: Moderate (CVSS 3.1 base 5.3) - **Consequences**: Any application that forwards attacker-controlled key names into `_.unset` or `_.omit` (e.g., from JSON request input) can have built-in prototype methods deleted globally, leading to denial-of-service or unexpected behavior across the entire process. ## Root Cause The root cause lies in the `baseUnset` function (shared by `_.unset` and `_.omit`). Before the fix, `baseUnset` parsed the path string into segments and walked each segment on the target object without validating whether a segment was a dangerous prototype accessor such as `__proto__`, `constructor`, or `prototype`. Because `someObject.__proto__` returns the live `Object.prototype`, a crafted path like `__proto__.toString` causes the function to traverse out of the intended object and into the global prototype, then delete the specified key from it. **Fix commit**: [`edadd452146f7e4bad4ea684e955708931d84d81`](https://github.com/lodash/lodash/commit/edadd452146f7e4bad4ea684e955708931d84d81) The fix adds a loop in `baseUnset` that iterates over path segments and: 1. Skips non-string keys. 2. Blocks `__proto__` anywhere in the path if it is not an own property of the current object. 3. Blocks `constructor.prototype` chains, with a narrow exception for primitive roots (e.g., `_.unset(0, 'constructor.prototype.a')`). ## Reproduction Steps The reproduction script is `repro/reproduction_steps.sh`. What the script does: 1. Installs the last published vulnerable version (`lodash@4.17.21`) in a temporary directory. 2. Runs a Node.js harness (`test_app.js`) that simulates an application receiving untrusted user input (`__proto__.toString`) and passing it to `_.unset({}, untrustedInput)`. 3. Captures the `typeof Object.prototype.toString` before and after the call. 4. Repeats steps 1–3 with the fixed version (`lodash@4.17.23`). 5. Also runs a secondary check with `_.omit({}, '__proto__.toString')` on the vulnerable build. 6. Writes a JSON runtime manifest (`repro/runtime_manifest.json`) with versions, exit codes, log paths, and explicit confirmation markers. **Expected evidence of reproduction:** - **Vulnerable build**: `typeof Object.prototype.toString` changes from `"function"` to `"undefined"`, and the harness exits with code `2` emitting `VULN_CONFIRMED`. - **Fixed build**: `typeof Object.prototype.toString` stays `"function"`, and the harness exits with code `0` emitting `FIX_CONFIRMED`. ## Evidence Log files captured by the script: - `logs/vuln_unset.log` — Vulnerable `_.unset` test output. - `logs/fixed_unset.log` — Fixed `_.unset` test output. - `logs/vuln_omit.log` — Vulnerable `_.omit` test output. - `repro/runtime_manifest.json` — Structured manifest linking all evidence. Key excerpts from `logs/vuln_unset.log`: ``` lodash version: 4.17.21 untrusted path input: __proto__.toString typeof Object.prototype.toString BEFORE: function typeof Object.prototype.toString AFTER: undefined VULN_CONFIRMED: prototype pollution via _.unset in lodash 4.17.21 ``` Key excerpts from `logs/fixed_unset.log`: ``` lodash version: 4.17.23 untrusted path input: __proto__.toString typeof Object.prototype.toString BEFORE: function typeof Object.prototype.toString AFTER: function FIX_CONFIRMED: prototype intact in lodash 4.17.23 ``` Key excerpts from `logs/vuln_omit.log`: ``` lodash version: 4.17.21 typeof Object.prototype.toString BEFORE: function typeof Object.prototype.toString AFTER: undefined VULN_CONFIRMED_OMIT: _.omit pollutes prototype ``` Environment: - Node.js v22.22.2 - npm 10.9.7 ## Recommendations / Next Steps - **Upgrade guidance**: Projects using lodash 4.17.21 or earlier should upgrade to 4.17.23 or later immediately. Note that a separate array-path bypass (CVE-2026-2950) exists and is fixed in 4.18.0, so upgrading to 4.18.0+ is recommended for full protection. - **Code review**: Audit all calls to `_.unset` and `_.omit` that accept user-controlled path strings. If upgrading is not immediately possible, sanitize path segments to reject `__proto__`, `constructor`, and `prototype` before passing them to lodash. - **Testing**: Add regression tests that exercise `__proto__` and `constructor.prototype` paths against `_.unset` and `_.omit` to prevent future regressions. ## Additional Notes - **Idempotency**: `repro/reproduction_steps.sh` has been executed twice consecutively with identical results (exit 0, same log outputs). - **Version caveat**: The ticket specifies 4.17.22 as the last vulnerable version, but this version was never published to npm or tagged in Git. The reproduction uses `4.17.21` as the vulnerable baseline because it is the last published version that exhibits the flaw. The fix commit `edadd452` is included in `4.17.23`, which is published and confirmed to block the attack. - **Edge cases**: The fix specifically targets *string* path segments. A later bypass using array paths (`['__proto__', 'toString']`) was assigned a separate CVE (CVE-2026-2950) and is out of scope for this ticket. ## Reproduction Details Reproduced: 2026-05-22T08:30:20.851Z Duration: 1767 seconds Tool calls: 149 Turns: 139 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00134 pruva-verify GHSA-xxjr-mmjv-4gpg pruva-verify CVE-2025-13465 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00134&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00134/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 - GitHub Advisory: https://github.com/advisories/GHSA-xxjr-mmjv-4gpg - NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-13465 - Source: https://github.com/lodash/lodash ## Artifacts - repro/rca_report.md (analysis, 5747 bytes) - repro/reproduction_steps.sh (reproduction_script, 5221 bytes) - vuln_variant/rca_report.md (analysis, 7541 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 7210 bytes) - bundle/context.json (other, 2935 bytes) - bundle/metadata.json (other, 780 bytes) - bundle/ticket.md (ticket, 4003 bytes) - repro/runtime_manifest.json (other, 1254 bytes) - repro/validation_verdict.json (other, 1787 bytes) - vuln_variant/test_bypass.js (other, 5435 bytes) - vuln_variant/root_cause_equivalence.json (other, 1256 bytes) - vuln_variant/patch_analysis.md (documentation, 7213 bytes) - vuln_variant/variant_manifest.json (other, 2652 bytes) - vuln_variant/runtime_manifest.json (other, 1926 bytes) - vuln_variant/validation_verdict.json (other, 2842 bytes) - vuln_variant/source_identity.json (other, 843 bytes) - logs/vuln_omit.log (log, 171 bytes) - logs/vuln_variant/vuln_test.log (log, 143 bytes) - logs/vuln_variant/latest_test.log (log, 131 bytes) - logs/vuln_variant/fixed_test.log (log, 579 bytes) - logs/vuln_unset.log (log, 271 bytes) - logs/fixed_unset.log (log, 254 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00134 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00134/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00134 ## 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