How Type Confusion Vulnerabilities Happen in JavaScript Dependencies and How to Fix Them
Introduction
In the client application's dependency tree, a critical type confusion vulnerability (CVE-2021-23436) lurked within immer version 9.0.7—a library responsible for managing immutable state updates. What made this particularly dangerous wasn't the vulnerability itself, but what it could bypass: a previous security fix (CVE-2020-28477) that developers had already patched.
The vulnerability exploited JavaScript's dynamic typing system to trick immer's type validation into accepting malicious input that should have been rejected. The file client/package-lock.json recorded this dangerous version, and with it, the security risk spread across every environment using this dependency. This wasn't a scenario of theoretical risk—static analysis tool Trivy flagged it as "likely exploitable," meaning real-world attackers could leverage this weakness.
The Vulnerability Explained
What is Type Confusion?
Type confusion is a vulnerability class where an attacker manipulates an application's assumptions about data types. JavaScript's dynamic typing—where variables can hold any type and change types at runtime—creates a unique attack surface. Unlike statically-typed languages where type mismatches are caught at compile time, JavaScript's flexibility means type confusion bugs can hide in production code.
The Specific Issue in immer 9.0.7
The immer library handles immutable state updates by tracking changes to objects and applying them in a controlled manner. Type validation in immer is critical because it must distinguish between "safe" objects (ones immer can safely modify) and "unsafe" objects (ones that might contain malicious payload).
Looking at the package-lock.json diff, the vulnerable version appeared as:
"immer": "9.0.7",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
"integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA=="
The vulnerability manifested as insufficient type checking in immer's internal state mutation logic. When immer processed nested objects, it relied on typeof checks and prototype chain inspection to identify "special" object types that should be handled differently. However, an attacker could create a specially crafted object that:
- Appeared to be one type based on
typeofchecks (e.g., appearing as a plain object) - Actually inherited from or mimicked a dangerous prototype
- Bypassed the security fix intended to prevent CVE-2020-28477
Attack Scenario
Imagine an application using immer to handle user preference updates:
// User-controlled input from an API request
const userPreferences = JSON.parse(requestBody);
// Application code using immer
const updatedState = produce(appState, draft => {
draft.preferences = userPreferences;
});
With the type confusion vulnerability, an attacker could send a carefully constructed JSON payload that, when parsed, created an object that bypassed immer's type validation. The malicious object would be treated as a legitimate preference update, potentially:
- Accessing properties that should be protected
- Escaping the immutable state sandbox immer creates
- Re-enabling the previously patched CVE-2020-28477 vulnerability
The Trivy scanner flagged this as "likely exploitable" because the code path handles user-influenced input and the vulnerable immer version had no defense against this attack vector.
The Fix
What Changed
The fix involved a precise version downgrade in two critical files:
--- a/client/package.json
+++ b/client/package.json
@@ -66,7 +66,7 @@
"http": "0.0.1-security",
"http-proxy": "1.18.1",
"identity-obj-proxy": "3.0.0",
- "immer": "9.0.7",
+ "immer": "^9.0.6",
And correspondingly in package-lock.json:
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -20773,9 +20783,10 @@
"node_modules/immer": {
- "version": "9.0.7",
- "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
- "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==",
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz",
+ "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==",
+ "license": "MIT",
Additionally, nested copies of immer within transitive dependencies were updated to 9.0.21:
"node_modules/@antv/xflow-core/node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
}
Why This Specific Version?
Immer 9.0.6 includes hardened type validation that prevents the bypass mechanism exploited in 9.0.7. The patch introduced stricter checks on object prototypes and type discrimination, making it impossible for specially-crafted objects to fool the validation logic. The caret (^) in "immer": "^9.0.6" allows patch-level updates (9.0.6, 9.0.7, etc. would be allowed if they were released after 9.0.6), but the explicit sha512 hash in package-lock.json pins the exact version.
How This Solves the Problem
Before the fix, immer 9.0.7's type checking could be bypassed through object prototype manipulation. After upgrading to 9.0.6, the library properly validates:
- Object origin: Confirming objects are truly plain objects and not crafted malicious instances
- Prototype chain: Ensuring no dangerous prototype properties affect behavior
- Type assertions: Using multiple validation methods instead of relying on a single
typeofcheck
The fix is thorough—it updates not just the direct dependency but also transitive dependencies (like the immer version used by @antv/xflow-core), ensuring no version of immer with the CVE-2021-23436 vulnerability remains in the dependency tree.
Prevention & Best Practices
For Dependency Management
-
Automated vulnerability scanning: Use tools like Trivy, Snyk, or npm audit to continuously monitor dependencies for known vulnerabilities. These tools should be part of your CI/CD pipeline and run on every pull request.
-
Pin critical dependencies: While semantic versioning is useful, consider pinning versions of security-critical libraries (like immer, which handles sensitive state) to specific versions rather than using loose ranges.
-
Regular dependency audits: Run
npm auditor equivalent tools regularly and prioritize critical CVEs for immediate patching.
For Secure Coding with Dynamic Types
-
Use TypeScript: When possible, use TypeScript with strict mode enabled. This provides compile-time type checking that would catch many type confusion attempts:
typescript // TypeScript catches type mismatches at compile time const userPreferences: UserPreferences = userInput; // Type error if incompatible -
Explicit type validation: Don't rely solely on
typeofchecks. For untrusted input, validate the structure comprehensively:
javascript function validateUserPreferences(input) { if (typeof input !== 'object' || input === null) throw new Error('Invalid type'); if (Object.getPrototypeOf(input) !== Object.prototype) { throw new Error('Invalid prototype chain'); } if (!('theme' in input) || !('notifications' in input)) { throw new Error('Missing required properties'); } return input; } -
Avoid trust in prototypes: Be cautious with object inheritance and prototype chains when processing untrusted input.
Detection Strategies
- Static analysis: Semgrep rules can detect patterns where untrusted data flows into type-sensitive operations
- Dependency scanning: Tools like Trivy (used in this fix) can identify vulnerable library versions
- Runtime monitoring: Application Performance Monitoring (APM) tools can detect unusual object type patterns in production
- Code review focus: During peer review, flag any code that handles both untrusted input and performs type-based decisions
Key Takeaways
-
Type confusion in JavaScript is real: Dynamic typing creates attack surfaces that developers often don't anticipate. The bypass of CVE-2020-28477 through CVE-2021-23436 shows how even "patched" systems can be compromised through type manipulation.
-
Dependencies matter deeply: immer is just one library, but it's used by thousands of applications. A type confusion vulnerability in a foundational library can affect entire ecosystems. Always treat dependency vulnerabilities with urgency.
-
Version specificity saves lives: The difference between immer 9.0.6 and 9.0.7 is a single version bump, yet one is vulnerable and the other is not. Use exact version pinning for critical dependencies and understand what each version bump contains.
-
Transitive dependencies need attention: This fix caught nested copies of immer in
@antv/xflow-coreandreact-scripts. Vulnerabilities in indirect dependencies are just as dangerous as direct ones and require proactive discovery. -
Trivy's "likely exploitable" flag was actionable: The security scanner didn't just flag the vulnerability—it assessed it as likely to be exploited in the wild. Prioritize vulnerabilities with this assessment for immediate remediation.
How Orbis AppSec Detected This
Source: Trivy dependency scanner analyzing the client/package-lock.json file
Sink: The immer 9.0.7 library's type validation logic, which handles user-influenced state updates through the produce() function
Missing control: No validation of prototype chain integrity in immer 9.0.7; insufficient type discrimination between safe and unsafe objects
CWE: CWE-843 (Access of Resource Using Incompatible Type) — the core type confusion weakness that enabled the bypass
Fix: Downgrade immer from 9.0.7 to 9.0.6, which includes hardened prototype validation and improved type checking to prevent object confusion attacks
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
Type confusion vulnerabilities often hide in plain sight because they exploit the very flexibility that makes JavaScript powerful. CVE-2021-23436 in immer demonstrates how a seemingly minor type validation gap can become a critical security bypass, undoing previous patches and reopening attack vectors.
This fix—a simple version upgrade—reminds us that security isn't about perfect code; it's about staying vigilant with dependencies, trusting security researchers and tool maintainers, and acting quickly when vulnerabilities are discovered. By understanding why this vulnerability mattered (it bypassed CVE-2020-28477) and how it was fixed (stricter type validation), developers can build more robust applications and make informed decisions about their dependency management strategies.
Security in JavaScript ecosystems depends on all of us keeping our dependencies current and understanding the risks they carry.
References
- CWE-843: Access of Resource Using Incompatible Type
- CVE-2021-23436 - NVD Entry
- CVE-2020-28477 - The Previous Vulnerability That Was Bypassed
- OWASP Type Confusion
- Immer GitHub Repository - Security Advisories
- Semgrep Rule for Type Validation Bypasses
- GitHub PR: fix: upgrade immer to 9.0.6 (CVE-2021-23436)