A widely used utility library for asynchronous control flow in Node.js, async, shipped for years with a prototype-pollution weakness in the internal iterator handling used by its collection-processing functions. Tracked as CVE-2021-43138, the flaw allowed an attacker who could influence certain input values passed into async's iteration helpers to pollute Object.prototype. On its own, that produces a denial-of-service condition; in applications that later read attacker-controlled prototype properties back out and used them in unsafe ways (for example, in dynamic code paths or template evaluation), the same weakness could escalate into arbitrary code injection. Because async sits deep in the dependency tree of an enormous share of the npm ecosystem — pulled in transitively by build tools, test runners, and countless application frameworks — the practical blast radius of this CVE was defined less by the library itself and more by what downstream code did with polluted objects.
This post breaks down what is confirmed about CVE-2021-43138, which versions are affected, how it was scored, and what remediation looks like for teams that still have old async versions lurking in their dependency graphs.
What the vulnerability is
async provides higher-order functions — each, map, mapValues, every, and their *Series and *Limit variants, among others — that iterate over collections and invoke a callback for every item. The vulnerability lived in the internal machinery async uses to walk collection keys during iteration. Under certain conditions, an attacker who could control the shape or keys of the collection being iterated could cause the library to write to Object.prototype rather than to the intended object. That's the textbook definition of a prototype pollution bug: it corrupts the base object that every other object in the JavaScript runtime inherits from.
Prototype pollution bugs are dangerous less because of the write itself and more because of what happens next. If any code downstream — in the application, in another dependency, or in a templating/config layer — reads a property off an object without checking whether it's an "own" property, it can pick up the attacker's polluted value instead. Depending on how that value is consumed, the result ranges from unexpected application behavior, to a crash (DoS), to full code injection if the polluted property flows into something that gets evaluated or executed. That escalation path is why this CVE is filed as a code injection risk rather than a plain DoS bug — the underlying primitive is prototype pollution, and code injection is the realistic worst-case outcome depending on the consuming application.
Affected versions and components
- Package:
async(npm) - Affected: versions of the 2.x line prior to 2.6.4, and versions of the 3.x line prior to 3.2.2
- Fixed in: 2.6.4 and 3.2.2
Any project — direct or transitive — pinned below these fixed versions is exposed. Given async's popularity as a foundational utility, it is common to find it several layers deep in a dependency tree, pulled in by build tooling, testing frameworks, or other libraries rather than declared directly in package.json. That indirection is exactly why this class of CVE is easy to miss in a manual review and why automated dependency graph visibility matters.
Severity, exploitability, and KEV status
CVE-2021-43138 was assigned a High severity rating, consistent with NVD's CVSS v3.1 scoring for prototype pollution issues with a realistic path to code execution rather than DoS alone. As with most prototype pollution CVEs, the practical exploitability is highly context-dependent: it hinges on whether a specific application actually passes attacker-influenced data into the vulnerable iterator logic, and whether anything downstream trusts polluted prototype properties in an unsafe way. That conditional exploitability is a common pattern across the broader family of npm prototype-pollution disclosures from 2020–2022 (lodash, minimist, and others share the same shape of risk).
This CVE has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and there is no public evidence of widespread active exploitation in the wild. EPSS-style exploitation-probability scoring for this class of library-level prototype pollution bug tends to sit low, reflecting that weaponizing it requires a specific, favorable application context rather than a generic remote exploit. That said, "low observed exploitation" is not the same as "safe to ignore" — supply chain risk from a popular transitive dependency compounds across every application that includes it, and a low EPSS score today does not preclude a future public proof-of-concept changing that calculation.
Timeline
- Disclosure: The prototype pollution weakness in
async's iterator handling was reported and published as a GitHub Security Advisory alongside the CVE assignment in late 2021. - Remediation: The maintainers shipped fixed releases on both active lines — 2.6.4 for the legacy 2.x branch and 3.2.2 for the current 3.x branch — closing the pollution path in the iterator logic.
- Post-disclosure: As with most widely-depended-upon utility packages, uptake of the fix has been gradual. Because
asyncis frequently a transitive dependency rather than a direct one, many projects only receive the patched version when an upstream dependency bumps its ownasyncrequirement, or when a lockfile is regenerated — which means vulnerable versions can persist in build artifacts and container images well after a fix is available.
Remediation steps
- Identify exposure. Run
npm ls async(or the equivalent for Yarn/pnpm) across every service and repository to find every direct and transitive instance of the package, then check the resolved version against 2.6.4 / 3.2.2. - Upgrade explicitly. Where
asyncis a direct dependency, bump it to^2.6.4or^3.2.2(or later) inpackage.jsonand regenerate the lockfile. Where it's transitive, upgrade the parent dependency that pulls it in, or use your package manager's override/resolution mechanism (overridesin npm,resolutionsin Yarn,pnpm.overrides) to force a safe version until the upstream package catches up. - Rebuild and re-scan container images and build artifacts. A patched
package-lock.jsondoesn't help if a previously built image or cached artifact still bundles the vulnerable version. Rebuild from a clean dependency install and re-scan the resulting artifact. - Audit prototype-pollution defenses in your own code, independent of this specific CVE. Avoid patterns that read arbitrary keys off untrusted objects without an
Object.prototype.hasOwnProperty.call()check, and preferObject.create(null)orMapfor objects that hold user-controlled keys. This CVE's escalation path — pollution to code injection — depends entirely on how your application consumes objects, so hardening that layer reduces risk from this and future, similar bugs. - Re-run CI dependency and SCA scans after upgrading to confirm the advisory no longer flags, and treat the fix like any other dependency bump — pin it, test it, and ship it through your normal change process rather than as a break-glass hotfix unless you have evidence of active targeting.
How Safeguard Helps
CVE-2021-43138 is a good illustration of why supply chain security can't stop at "do we have a CVE scanner." The vulnerable package is rarely declared directly — it's buried three or four levels deep in a dependency tree, which means teams relying on manual reviews or point-in-time audits routinely miss it until a scanner flags it, often long after the patched versions have shipped.
Safeguard continuously maps the full dependency graph — direct and transitive — across every repository and build artifact, so a case like async before 2.6.4/3.2.2 surfaces automatically rather than depending on someone remembering to check a rarely-touched utility library. Because Safeguard correlates dependency inventory with build and deployment metadata, it can tell you not just that a vulnerable version exists in a lockfile, but whether it actually made it into a shipped artifact or running service — cutting through the noise of CVEs that are present on disk but never reach production.
For prototype-pollution-class findings specifically, Safeguard's risk scoring accounts for exploitability context rather than surfacing every CVE with equal urgency: severity rating, KEV status, and real-world exploitation signals are combined so teams can prioritize the handful of findings that matter over the long tail of low-risk transitive advisories. And because remediation for this class of bug often means bumping an upstream dependency rather than the vulnerable package directly, Safeguard traces the dependency chain back to the actionable fix — the parent package or override that needs to change — instead of just naming the leaf-level library.
The broader lesson from CVE-2021-43138 holds for the next transitive npm advisory too: visibility into what's actually running, not just what's declared, is what turns a CVE disclosure into a fast, confident remediation rather than a scramble.