## Ticket: CVE-2025-13465 — Lodash prototype pollution in `_.unset` / `_.omit`

**Advisory**: GHSA-xxjr-mmjv-4gpg — https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg
**CVE**: CVE-2025-13465 | **CWE-1321** (Prototype Pollution)
**Severity**: Moderate — NVD CVSS 3.1 base 5.3 (AV:N/AC:L/PR:N/UI:N), CVSS 4.0 base 6.9
**Package**: `lodash` (npm) | **Repository**: https://github.com/lodash/lodash
**Published**: 2026-01-21

### Impact

`lodash`'s `_.unset(object, path)` and `_.omit(object, paths)` accept a string
`path`, parse it into segments (dot / bracket notation), and walk those
segments on the target object. Through `4.17.22` they do **not** reject
dangerous segments such as `__proto__`, `constructor`, or `prototype`.

Because `someObject.__proto__` is the *live* `Object.prototype`, a crafted path
string lets an attacker steer `_.unset` out of the intended object and into a
built-in prototype, then **delete a method from it**. The deletion affects the
prototype globally for the rest of the Node.js process, so every object of
that type loses the method — a denial-of-service / integrity issue
(prototype pollution by deletion). It permits *deleting* members but not
overwriting their behavior.

Any code that forwards attacker-influenced key names into `_.unset` / `_.omit`
(for example a property path taken from JSON request input) is exploitable.

### Affected / fixed versions

Affected: `lodash` `4.0.0` → `4.17.22` (inclusive).
Fixed: **`4.17.23`**.

Reproduce on a vulnerable build (**`4.17.22`**) and verify the fix on
**`4.17.23`**.

> Note: the `4.17.23` fix only guards *string* path segments. A later
> array-path bypass is a **separate** CVE (CVE-2026-2950, fixed in `4.18.0`)
> and is out of scope here — the PoC for this ticket must use a plain string
> path so that it succeeds on `4.17.22` and is blocked on `4.17.23`.

### Where to look

The fix ships in commit
[`edadd452146f7e4bad4ea684e955708931d84d81`](https://github.com/lodash/lodash/commit/edadd452146f7e4bad4ea684e955708931d84d81).
Inspect the patch to confirm the root cause and the guard:

```bash
git clone https://github.com/lodash/lodash.git
cd lodash && git show edadd452146f7e4bad4ea684e955708931d84d81
```

The change adds a check that rejects dangerous string key segments
(`__proto__`, `constructor`, `prototype`) when `_.unset` / `_.omit` walk a
parsed path.

### Reproduction approach

No service, database, or browser is needed — the bug is observable purely
in-process with Node.js.

1. In a scratch directory, install the **vulnerable** build:
   `npm install lodash@4.17.22`
2. Run a small Node script that:
   - records `typeof Object.prototype.toString` (baseline: `"function"`),
   - calls `_.unset({}, '__proto__.toString')`,
   - records `typeof Object.prototype.toString` again.
3. Repeat with the **fixed** build: `npm install lodash@4.17.23`.

Use `_.unset` as the primary vector; `_.omit` exhibits the same flaw and can be
shown as a secondary check.

### Expected result

| Build | `_.unset({}, '__proto__.toString')` | `Object.prototype.toString` after |
|-------|-------------------------------------|-----------------------------------|
| `lodash@4.17.22` (vulnerable) | path segment accepted, traverses into `Object.prototype` | **deleted** — `typeof` becomes `"undefined"` |
| `lodash@4.17.23` (fixed) | `__proto__` segment rejected by the guard | **intact** — `typeof` stays `"function"` |

- **Vulnerable indicator**: after the call, `Object.prototype.toString` is gone
  (`typeof === "undefined"`); the global prototype has been polluted.
- **Fixed indicator**: the method is untouched; `_.unset` refused the
  dangerous path.

### Expected artifacts

- `reproduction_steps.sh` — installs both versions and runs the PoC,
  printing the before/after state for each.
- `validation_verdict.json` — `verdict: confirmed`, with the vulnerable and
  fixed indicators captured.
- Logs capturing the script output for `4.17.22` and `4.17.23`.
