Share
## https://sploitus.com/exploit?id=622FE954-B229-5DE1-B058-7F59DD5B727C
# CVE-2026-26903 PoC

**Denial-of-service via unbounded recursion in TanStack Query's `replaceEqualDeep` function**

A single crafted query update with deeply nested objects can **freeze the JavaScript thread indefinitely**, causing complete application unresponsiveness. No authentication or special permissions are required.

## Affected Versions

This vulnerability affects TanStack Query (React Query) versions that include the vulnerable `replaceEqualDeep` implementation:

| Package | Affected Versions | Status |
|---------|-------------------|--------|
| @tanstack/react-query | v4.x - v5.x | **PATCHED** |
| @tanstack/query-core | v4.x - v5.x | **PATCHED** |
| react-query | v3.x (legacy) | **PATCHED** |

### Official Description

> **CVE-2026-26903: Denial of Service via unbounded recursion in replaceEqualDeep** (Severity: Medium)
>
> The `replaceEqualDeep` function in TanStack Query performs recursive comparison of nested objects without depth limits. When processing deeply nested data structures, this can trigger unbounded recursion leading to stack overflow and application freeze.
>
> โ€” Assigned via MITRE/GitHub Security Advisory

### CVSS Score

**CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L** โ€” **5.3 (Medium)**

- **Attack Vector**: Network (client-side JavaScript)
- **Attack Complexity**: Low
- **Privileges Required**: None
- **User Interaction**: None (automatic on query updates)
- **Scope**: Unchanged
- **Impact**: Availability only (complete DoS)

## Vulnerability Summary

TanStack Query's `replaceEqualDeep` function is used internally to determine if query data has actually changed, optimizing re-renders by preserving object references when possible. The function recursively traverses object properties to perform deep equality comparison.

### The Problem

The recursive implementation lacks depth limits or cycle detection:

```javascript
function replaceEqualDeep(a, b) {
  // ... type checks ...
  
  for (let i = 0; i (a: unknown, b: T): T
- export function replaceEqualDeep(a: any, b: any): any {
+ export function replaceEqualDeep(a: unknown, b: T, depth?: number): T
+ export function replaceEqualDeep(a: any, b: any, depth = 0): any {
   if (a === b) {
     return a
   }
 
+  if (depth > 500) return b
+
   const array = isPlainArray(a) && isPlainArray(b)
 
   if (!array && !(isPlainObject(a) && isPlainObject(b))) return b
   
   // ... middle section unchanged ...
   
-    const v = replaceEqualDeep(aItem, bItem)
+    const v = replaceEqualDeep(aItem, bItem, depth + 1)
     copy[key] = v
     if (v === aItem) equalItems++
```
 
**Key changes:**
 
1. **Added `depth` parameter** with default value `0` to track recursion depth
2. **Added depth limit check** โ€” returns `b` immediately if `depth > 500`
3. **Incremented depth in recursive calls** โ€” `replaceEqualDeep(aItem, bItem, depth + 1)`
This simple fix prevents stack overflow while allowing reasonable nesting (500 levels is more than sufficient for real-world data structures).

## Mitigation

### Temporary Workarounds

1. **Flatten deeply nested data** before passing to TanStack Query
2. **Implement custom comparison functions** with depth limits
3. **Monitor query data complexity** in development

### Proper Fix

Implement depth limiting in `replaceEqualDeep`:

```javascript
function replaceEqualDeep(a, b, depth = 0) {
  if (depth > MAX_DEPTH) return b; // Prevent deep recursion
  if (a === b) return a;
  
  // ... existing logic ...
  
  const v = replaceEqualDeep(a[key], b[key], depth + 1);
  
  // ... rest of function
}
```

Or use iterative traversal instead of recursion.


## References


- [GitHub Security Advisory](https://github.com/TanStack/query/commit/269351b8ce4b4846da3d320ac5b850ee6aada0d6)

## Disclaimer

This proof-of-concept is provided for **educational and authorized security testing purposes only**. Use it responsibly and only against applications you own or have explicit permission to test. The authors are not responsible for any misuse of this information.