In July 2020, researchers disclosed CVE-2020-8203, a prototype pollution vulnerability in lodash affecting the zipObjectDeep function. An attacker who can influence the arguments passed into zipObjectDeep — directly or through application logic that forwards user-controlled data into it — can inject a __proto__ key that pollutes Object.prototype. Because JavaScript objects inherit from Object.prototype by default, a successful pollution can corrupt application behavior globally: triggering denial of service, bypassing security checks that rely on property existence, or, in the right application context, escalating to remote code execution. Given that lodash sits in the dependency tree of an enormous share of the JavaScript ecosystem — often several layers deep and invisible to the teams actually shipping the vulnerable code — this issue had outsized blast radius relative to its technical footprint.
What is prototype pollution, and why does zipObjectDeep matter
Prototype pollution is a JavaScript-specific vulnerability class where an attacker manipulates an object's prototype chain — typically by injecting a __proto__, constructor.prototype, or similar key into a merge, clone, or "set path" operation. Because prototypes are shared across all instances of an object type, polluting Object.prototype effectively injects attacker-controlled properties into every object in the running process, unless the application explicitly guards against it.
zipObjectDeep is a lodash utility that takes an array of property paths and an array of values, then builds a nested object by mapping each path to its corresponding value. That "build a nested object from an arbitrary path" behavior is exactly the pattern that enables prototype pollution: if the path array contains a string like __proto__.polluted, lodash's internal path-setting logic (shared with functions like _.set and _.zipObjectDeep) walks the path segment by segment and, prior to the fix, did not stop at the __proto__ boundary. The result is that a value the attacker chose gets written directly onto the shared prototype object rather than onto the intended target object.
In practice, exploitation requires an application to pass attacker-influenced arrays into zipObjectDeep — for example, building keys and values from query parameters, form fields, or deserialized JSON before zipping them into a config object. That dependency on application-specific data flow is why this bug is best evaluated through reachability, not just presence of the vulnerable package (more on that below).
Affected versions and components
- Affected: lodash versions prior to 4.17.19, including the widely-used 4.x line dating back several years.
- Fixed in: lodash 4.17.19 and later.
- Affected function:
zipObjectDeep(the underlying path-assignment logic it shares with other "deep" lodash utilities was hardened as part of the same fix). - Distribution surface: lodash is one of the most depended-upon packages in the npm registry, pulled in both directly and transitively by thousands of frameworks, build tools, and application libraries. Many organizations run vulnerable lodash versions without ever having added it to
package.jsonthemselves — it arrives nested three, four, or five levels deep via other dependencies. - Related advisories: This CVE is one of several lodash prototype-pollution issues disclosed around the same period (alongside earlier fixes for
defaultsDeep,merge, andset-family functions), reflecting a broader pattern of prototype-pollution weaknesses in deep-merge/deep-path utilities across the JavaScript ecosystem, not just in lodash.
CVSS, EPSS, and KEV context
CVE-2020-8203 was assigned a High-severity CVSS v3.1 base score in the 7.4 range, reflecting network attack vector, low attack complexity, no privileges required, but a need for some user interaction or a specific data-flow condition to reach the vulnerable sink — consistent with the fact that exploitability depends on how a given application uses zipObjectDeep.
From an exploitation-likelihood standpoint, published EPSS scoring has historically placed this CVE in the low single-digit percentile range, which tracks with reality: prototype pollution in a utility function is a serious primitive, but it is not a drop-in remote code execution exploit against every application that merely has the package installed. Impact depends heavily on what the host application does with polluted properties downstream (for example, whether polluted values later influence template rendering, command construction, or authentication/authorization checks).
As of this writing, CVE-2020-8203 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog, meaning there is no confirmed record of active, widespread in-the-wild exploitation being tracked at the federal level. Security teams should read that as "not currently a KEV-mandated emergency patch," not as "safe to ignore" — prototype pollution primitives are frequently chained with application-specific gadgets to achieve real impact, and the lack of a KEV listing mainly reflects the difficulty of fingerprinting this specific exploitation pattern at scale, not an absence of risk.
Timeline
- Prior to disclosure: lodash's
zipObjectDeepand related deep path-assignment logic did not sanitize__proto__segments when constructing nested objects from path arrays. - Disclosure (2020): Security researchers identified and reported the prototype pollution weakness in
zipObjectDeep, following a wave of similar prototype-pollution research targeting popular JavaScript utility libraries throughout 2019–2020. - Patch released — lodash 4.17.19 (July 2020): The lodash maintainers shipped a fix hardening the internal path-assignment routines against
__proto__and constructor-chain injection. - Public advisory and NVD entry: CVE-2020-8203 was published with a GitHub Security Advisory and NVD record documenting the affected versions, the fix version, and CVSS scoring.
- Ongoing exposure: Because lodash is so deeply nested across the dependency graph, organizations continued (and in some cases still continue) to ship vulnerable transitive copies of lodash well after the fix was available, simply because no direct dependency owner triggered an upgrade.
Remediation steps
- Upgrade lodash to 4.17.19 or later. For direct dependencies, bump the version in
package.jsonand regenerate your lockfile. Runnpm ls lodashoryarn why lodashto confirm the resolved version across your dependency tree, since apackage.jsonbump alone does not guarantee every nested copy is upgraded. - Audit transitive dependencies. Use
npm audit,yarn audit, or your SBOM/SCA tooling to identify every location lodash is pulled in transitively, and useoverrides(npm) orresolutions(yarn) to force a minimum safe version across the whole tree if upstream maintainers of intermediate packages haven't updated yet. - Freeze or guard
Object.prototypewhere feasible. For services under your direct control, considerObject.freeze(Object.prototype)in hardened runtime contexts, or adopt a library-level allowlist that strips__proto__,constructor, andprototypekeys from any object built out of user-controlled path/value pairs before they reach deep-merge or deep-set utilities. - Review call sites, not just package versions. Search your codebase for calls to
_.zipObjectDeep,_.set,_.merge, and_.defaultsDeepwhere either argument can be influenced by request input, query strings, uploaded files, or deserialized JSON. Even after patching lodash itself, application-level logic that blindly trusts user-supplied keys can reintroduce similar risk through other utilities. - Add regression tests for prototype pollution. Include test cases that attempt to pollute
Object.prototypevia__proto__injection into any function that accepts user-influenced paths, and assert that({}).pollutedremainsundefinedafter the call. - Re-scan and re-baseline after remediation. Confirm via SCA/SBOM tooling that no vulnerable lodash version remains anywhere in the resolved dependency graph, including in build tooling, CI containers, and vendored bundles — not just the runtime
node_modulesof the primary application.
How Safeguard Helps
Safeguard is built to cut through exactly this kind of noisy-but-important CVE. Instead of flagging every project that merely has lodash <4.17.19 in its lockfile, Safeguard's reachability analysis traces whether your application code actually calls zipObjectDeep (or the related _.set/_.merge path-assignment logic) with data that originates from an untrusted source, so your team can triage the handful of genuinely exploitable call sites instead of chasing hundreds of false positives across a monorepo. Griffin, Safeguard's AI-powered security analyst, reads the surrounding application logic to explain in plain language how a polluted property could actually be abused in your specific service — turning a generic "lodash is vulnerable" alert into a concrete, prioritized risk statement. Safeguard's SBOM generation and ingest capabilities give you a continuously updated map of every transitive copy of lodash across your entire portfolio, including nested dependencies that never show up in a direct package.json review. And when remediation is confirmed necessary, Safeguard can open an auto-fix pull request that bumps lodash to a patched version (and applies dependency overrides where needed), so the fix lands in your codebase with the context and reachability evidence already attached for reviewer sign-off.