Safeguard
Open Source Security

lodash zipObjectDeep Prototype Pollution (CVE-2020-8203)

CVE-2020-8203 is a prototype pollution flaw in lodash's zipObjectDeep, affecting versions before 4.17.19. Here's the impact, timeline, and how to remediate it.

Aman Khan
AppSec Engineer
8 min read

In July 2020, security researchers disclosed CVE-2020-8203, a prototype pollution vulnerability in lodash, one of the most widely depended-upon JavaScript utility libraries in the npm ecosystem. The flaw lives in the zipObjectDeep function and lets an attacker who controls certain input values inject __proto__ keys that get merged directly onto Object.prototype. Once polluted, the global object prototype can alter application logic across an entire process, opening the door to denial of service and, depending on how the tainted properties are consumed downstream, more severe outcomes. Because lodash sits transitively beneath tens of thousands of packages, CVE-2020-8203 became one of the more consequential open source supply chain disclosures of 2020.

What is CVE-2020-8203?

Prototype pollution is a JavaScript-specific vulnerability class where an attacker manipulates an object's prototype (typically Object.prototype) instead of the object itself. Because every plain object in JavaScript inherits from Object.prototype by default, polluting it can silently change the behavior of unrelated code elsewhere in the application — code that never intentionally interacts with the attacker-controlled input.

zipObjectDeep is a lodash helper that builds a nested object from two arrays: one of property paths, and one of corresponding values. Internally, it relies on the same deep-assignment machinery used by functions like _.set and _.merge. Prior to the fix, lodash did not adequately filter dangerous keys — specifically __proto__ — when walking and constructing nested paths. An attacker who could influence the "paths" array passed into zipObjectDeep could supply a path such as ['__proto__', 'polluted'], causing lodash to write the corresponding value onto Object.prototype rather than onto the target object. From that point forward, every plain object in the running process could inherit the injected property, which is precisely why prototype pollution bugs are so hard to reason about: the impact surfaces far away from the vulnerable call site, in whatever code later checks for or trusts that property.

The vulnerability was catalogued publicly as CVE-2020-8203 and tracked via GitHub Security Advisory GHSA-p6mc-m468-83gw, alongside the npm advisory database entry for lodash.

Affected versions and components

  • Package: lodash (and by extension lodash-es and other lodash-derived bundles that shared the same zipObjectDeep implementation)
  • Affected versions: lodash versions prior to 4.17.19
  • Fixed version: 4.17.19, which patched the deep-assignment path handling to strip or reject dangerous keys like __proto__ and constructor.prototype
  • Function of concern: _.zipObjectDeep, plus related deep-path utilities that share the same underlying baseSet/baseZipObject logic

Because lodash is rarely a direct, top-level dependency for most JavaScript and Node.js projects — it's typically pulled in transitively through frameworks, build tools, and other npm utility libraries — the practical challenge for defenders was less "am I using lodash" and more "which of my dependencies bundles a vulnerable lodash, and does any code path actually reach zipObjectDeep with attacker-influenced input." This is a recurring theme in npm supply chain security: fixing a CVE in a leaf dependency often requires waiting on, or forcing, updates several layers up the dependency tree.

CVSS, EPSS, and KEV context

NVD scored CVE-2020-8203 as High severity under CVSS v3.1, reflecting network-exploitable conditions that require user interaction and elevate confidentiality, integrity, and availability impact once triggered — consistent with how prototype pollution bugs are generally scored, since the direct effect (corrupting shared object state) can cascade into denial of service or logic-bypass conditions depending on the consuming application.

It's worth being precise about what that severity score does and doesn't tell you. CVSS reflects the theoretical worst case for the library in isolation; it does not account for whether your specific application actually passes untrusted, attacker-controlled data into zipObjectDeep. This is why exploit prediction context matters: EPSS (Exploit Prediction Scoring System) probabilities for CVE-2020-8203 have remained comparatively low relative to internet-facing remote code execution bugs, reflecting that real-world exploitation of this specific lodash function requires a fairly specific code pattern to be reachable. CVE-2020-8203 has not appeared on CISA's Known Exploited Vulnerabilities (KEV) catalog, which tracks vulnerabilities with confirmed active exploitation in the wild. That combination — high theoretical severity, lower observed exploitation pressure — is common for prototype pollution findings in utility libraries, and it's exactly the kind of nuance that a pure CVSS-based prioritization approach misses.

None of that makes the vulnerability safe to ignore. Lodash's sheer reach means that even a low base-rate of "is this function reachable with untrusted input" translates into a large absolute number of exposed applications, and prototype pollution has repeatedly been chained with other application logic to achieve remote code execution in real incidents involving other libraries.

Timeline

  • Disclosure: The prototype pollution weakness in lodash's zipObjectDeep path was reported to the lodash maintainers and coordinated through the standard responsible disclosure process, resulting in the GHSA-p6mc-m468-83gw advisory.
  • Fix released: The lodash maintainers shipped version 4.17.19 in July 2020, patching the deep-assignment logic used by zipObjectDeep and related functions to reject dangerous prototype-chain keys.
  • CVE assignment and public tracking: CVE-2020-8203 was published to the National Vulnerability Database, and the npm advisory database and GitHub Advisory Database mirrored the fix guidance so that automated tooling (npm audit, Dependabot, and similar scanners) could flag affected installations.
  • Downstream propagation: In the months following the fix, the broader effect of CVE-2020-8203 played out less in lodash itself and more in the long tail of frameworks and libraries that had bundled or pinned older lodash versions, each of which needed its own release to bump the transitive dependency — a pattern typical of high-profile npm ecosystem vulnerabilities.

Remediation steps

  1. Upgrade lodash to 4.17.19 or later. If lodash is a direct dependency, bump the version in package.json and regenerate your lockfile. Version 4.17.21 is the current final 4.x release and includes fixes for this and several subsequent lodash CVEs, so upgrading straight to it is the pragmatic choice for most teams.
  2. Audit for transitive exposure. Run npm ls lodash (or the equivalent for yarn/pnpm) to identify every path through which a vulnerable lodash version enters your dependency tree. Because lodash is so frequently bundled, expect to find it several layers deep in build tooling, testing frameworks, and even other security tooling.
  3. Use overrides or resolutions where upstream hasn't patched. If a direct dependency still pins a pre-4.17.19 lodash and hasn't released a fix, use npm's overrides, yarn's resolutions, or pnpm's overrides field to force the patched version across the tree without waiting on every intermediate maintainer.
  4. Run npm audit / equivalent SCA tooling as a gate, not a one-off. CVE-2020-8203 is old enough now that any current npm audit run against a maintained project should flag it clearly if it's still present — treat a hit as a signal that a dependency chain has gone stale.
  5. Review code paths that call deep-assignment lodash functions with external input. Even after patching, it's good hygiene to check whether zipObjectDeep, _.set, _.merge, or _.merge variants are ever called with keys or paths derived from user-controlled request bodies, query strings, or JSON payloads, and add explicit denylisting of __proto__, constructor, and prototype at the application boundary as defense in depth.
  6. Re-scan after every dependency bump. Because prototype pollution fixes in a leaf package require every intermediate dependency to also bump its pin, a single outdated transitive dependency can silently reintroduce CVE-2020-8203 into a codebase that appears otherwise up to date.

How Safeguard Helps

Cases like CVE-2020-8203 illustrate exactly why supply chain security can't stop at "did we run npm audit once." Lodash's fix has been available since 2020, yet vulnerable versions still surface in modern dependency trees because they're reintroduced through transitive dependencies, vendored bundles, or lockfiles that were never refreshed. Safeguard is built to catch that drift continuously rather than at a single point in time.

Safeguard's software composition analysis continuously inventories every direct and transitive npm dependency across your repositories, so a reintroduced pre-4.17.19 lodash — however many layers deep it's buried — gets flagged the moment it enters a build, not months later during an audit. Rather than surfacing CVE-2020-8203 as a flat CVSS score, Safeguard correlates the finding with reachability: whether your application code actually exercises zipObjectDeep or related deep-assignment paths with externally influenced data, and whether EPSS and real-world exploitation signals justify urgent remediation versus routine patching. That context turns a long list of "vulnerable dependency" alerts into a prioritized, actionable queue.

Beyond detection, Safeguard integrates with CI/CD to block builds that introduce known-vulnerable lodash versions (or reintroduce them via a stale transitive pin), generates pull requests with the correct override or resolution entries, and maintains a software bill of materials so security and engineering teams always have an accurate, current answer to "are we exposed to CVE-2020-8203, and where." For a vulnerability this deeply embedded in the JavaScript ecosystem's dependency graph, that continuous, reachability-aware visibility is what actually prevents recurrence — not a one-time patch.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.