Prototype pollution is one of those JavaScript-specific bug classes that keeps resurfacing in the ecosystem's most heavily depended-upon packages, and lodash has produced more than its share of examples. CVE-2020-8203 is a prototype pollution vulnerability in lodash, reachable through the zipObjectDeep function, that lets an attacker who controls part of the input to that function inject properties onto Object.prototype. Because lodash sits in the dependency tree of an enormous share of the npm ecosystem — often several layers deep and invisible to the teams actually shipping the affected code — this single flaw touched an outsized number of downstream applications. Depending on how a given application consumes the polluted prototype, the practical impact ranges from denial of service to, in some application contexts, logic bypass or remote code execution.
This post breaks down what CVE-2020-8203 actually is, which versions and call paths are affected, what's known about its severity and exploitation likelihood, how the disclosure played out, and what remediation looks like — followed by how Safeguard helps teams catch issues like this before they ship.
What the vulnerability is
Prototype pollution occurs when untrusted input is used to set properties on an object in a way that an attacker can supply special keys — most commonly __proto__, but also constructor.prototype — that cause the assignment to land on the object's prototype chain rather than the object itself. Because JavaScript objects inherit from Object.prototype by default, polluting that prototype effectively injects a property onto every object in the running process, not just the one the attacker touched directly.
zipObjectDeep is a lodash utility that takes an array of property paths and an array of values and builds a nested object out of them, using lodash's internal deep-set logic. That internal deep-set logic is the same machinery implicated in several other lodash prototype-pollution advisories (defaultsDeep, merge, set, and related helpers all share code paths that need to guard against dangerous keys). In the vulnerable versions, zipObjectDeep did not adequately block path segments like __proto__, so an application that passes attacker-influenced path arrays into zipObjectDeep can have its global Object.prototype polluted with attacker-chosen keys and values.
The exploitability in any given application depends heavily on context: whether the attacker can actually control the path array passed to zipObjectDeep, and whether anything downstream in the application (property existence checks, template rendering, authorization flags, child_process invocations, etc.) trusts properties that could now silently "exist" on every object because of the polluted prototype. That's the recurring theme with prototype pollution bugs — the library bug is a precondition, but the impact is determined by what the consuming application does with polluted objects afterward.
Affected versions and components
- Component: lodash (the standalone
lodashnpm package, and by extensionlodash-es,lodash.zipobjectdeep, and other split packages that share the same vulnerable deep-set logic). - Vulnerable function:
zipObjectDeep. - Fixed in: lodash 4.17.19.
- Affected range: versions prior to the fixed release, spanning a large swath of lodash's 4.x line that had shipped
zipObjectDeep.
Because lodash is rarely a direct, top-level dependency for many of the projects that end up shipping it, the practical remediation challenge wasn't just "upgrade lodash" — it was identifying every place in a dependency tree where an outdated, transitively-included copy of lodash was actually present, since npm's flat-but-nested resolution can leave multiple lodash versions installed simultaneously across different subtrees.
CVSS, EPSS, and KEV context
CVE-2020-8203 was rated High severity under CVSS v3, reflecting that exploitation requires certain preconditions (an application path that feeds attacker-controlled data into zipObjectDeep) but can produce a meaningful confidentiality/integrity/availability impact once those preconditions are met — most commonly denial of service, with more severe outcomes possible in specific application contexts. As with most prototype-pollution CVEs, the CVSS vector reflects that this is a library-level primitive rather than a directly weaponizable exploit on its own; the base score should be read as an upper bound on impact assuming a vulnerable call path exists, not a guarantee that every application using lodash is remotely exploitable.
On the exploitation-likelihood side, CVE-2020-8203 has not attracted the kind of active, widespread exploitation that would land it in CISA's Known Exploited Vulnerabilities (KEV) catalog, and it is not currently listed there. EPSS scoring for this CVE has generally sat in a low-to-moderate band consistent with a widely known but rarely weaponized library flaw: plenty of automated scanners flag it because lodash is everywhere, but there is limited evidence of it being the initial access vector in real-world incidents compared to, say, deserialization or injection CVEs in internet-facing software. Teams should still treat "low EPSS" as a prioritization signal, not a reason to ignore the finding — prototype pollution in a dependency this ubiquitous is exactly the kind of long-tail risk that resurfaces years later in an unexpected code path.
Timeline
- Lodash had already patched earlier, related prototype-pollution issues in functions like
merge(CVE-2018-16487) anddefaultsDeep(CVE-2019-10744), so by 2020 the maintainers and the security research community were actively hunting for the same class of bug across the rest of lodash's deep-set helpers. - A researcher identified that
zipObjectDeepshared the same unguarded deep-set logic and could be used to polluteObject.prototype, and reported it upstream. - The lodash maintainers shipped a fix in lodash 4.17.19, hardening the internal path-setting logic against
__proto__and similar dangerous keys across the affected functions, includingzipObjectDeep. - The issue was assigned CVE-2020-8203 and published with a corresponding GitHub Security Advisory, after which dependency scanners, SCA tools, and
npm audit/yarn auditbegan flagging vulnerable, pre-4.17.19 lodash installs across the ecosystem. - In the following months, maintainers of countless downstream libraries and applications bumped their lodash pins or
resolutions/overridesentries, and the CVE became one of the most commonly cited examples of "transitive dependency" risk in supply chain security writeups — precisely because so many projects had lodash several layers deep in their tree without realizing it.
Remediation steps
- Upgrade lodash to 4.17.19 or later wherever it appears as a direct dependency. This is the primary fix and should be the default action for any project that lists lodash explicitly in
package.json. - Audit transitive copies. Run
npm ls lodash(or the equivalent for your package manager) to find every version actually installed innode_modules, since a project can have a patched top-level lodash while still shipping a vulnerable nested copy pulled in by an older transitive dependency. - Force resolution where upstream hasn't updated. Use
overrides(npm),resolutions(Yarn), orpnpm.overridesto pin every resolved lodash instance to a fixed version when a transitive dependency hasn't released its own patched release yet. - Check the split packages too. If your project uses granular packages like
lodash.zipobjectdeep,lodash.merge, orlodash.defaultsdeepinstead of the fulllodashpackage, verify those individual packages have also been updated, since they ship the same underlying logic on their own release cadence. - Re-scan after remediation. Confirm with
npm audit,yarn audit, or your SCA/SBOM tooling that no vulnerable lodash version remains anywhere in the resolved dependency tree, not just at the top level. - Review application-level exposure. Independent of the library patch, review any code path where user-controlled input reaches
zipObjectDeep,merge,defaultsDeep,set, or similar deep-object helpers, and add explicit denylisting of__proto__,constructor, andprototypekeys as defense in depth — library patches close the known vector, but the same input-sanitization discipline protects against the next prototype-pollution bug in whatever utility library you depend on next.
How Safeguard Helps
CVE-2020-8203 is a textbook case for why software supply chain security can't stop at "do we have lodash in package.json" — the real exposure lives in the transitive dependency graph, which is exactly where manual review breaks down. Safeguard is built to close that gap end to end:
- Continuous SCA and SBOM visibility surfaces every resolved version of lodash (and its split packages) across your entire dependency tree — including nested, transitive copies that
package.jsonalone won't reveal — so you know immediately whether CVE-2020-8203, or any newly disclosed CVE, actually reaches your codebase. - Risk-prioritized findings combine CVSS severity with EPSS-style exploitability signals and KEV status, so teams aren't stuck triaging every "High" finding with equal urgency. A ubiquitous but rarely-weaponized bug like this one gets ranked against your actual exposure, not just its raw score.
- Reachability and call-path context help distinguish "lodash is installed somewhere in my tree" from "attacker-controlled input can actually reach
zipObjectDeepin my running application," so engineering time goes toward the fixes that matter first. - Automated remediation guidance and pull requests identify the minimum version bump or override needed to close the vulnerable version range across every affected subtree, rather than leaving teams to hand-edit
overrides/resolutionsblocks one package at a time. - Policy gates in CI/CD catch the reintroduction of vulnerable lodash versions — or the next prototype-pollution CVE in a different dependency — before it merges, turning a one-time cleanup into durable protection.
For a vulnerability class that keeps recurring across the JavaScript ecosystem's most-used utility libraries, that combination of full dependency-graph visibility, reachability-aware prioritization, and automated remediation is what turns "we saw the CVE alert" into "we confirmed we were never exposed" or "we fixed it everywhere, provably."