diff --git a/FIDELITY.md b/FIDELITY.md index 95502e5..fd60f9a 100644 --- a/FIDELITY.md +++ b/FIDELITY.md @@ -18,6 +18,17 @@ The committed PNGs under `testdata/renders/` back every claim here. Reproduce them with the commands at the bottom. The measured-vs-Chrome numbers live in [`bench/REPORT.md`](bench/REPORT.md). +## 2026-10-17 (round 173) — the SAME memory-exhaustion shape as round 172, but simpler to trigger: a single `innerHTML` assignment, no loop at all, found by generalizing the lesson rather than re-checking the same code path (security audit, patch) + +Continuing round 172's own sweep (JS methods that can allocate unboundedly from a tree it already has) into the one other place a script routinely hands this engine a whole subtree at once: `innerHTML`/`outerHTML`/`insertAdjacentHTML`. All three ultimately call `dom.ParseFragment` (or, for the top-level page, `dom.Parse`), both of which share one core: `convertChildren`. + +- **Finding**: `golang.org/x/net/html`'s own parser refuses markup nested past 512 levels, but that guard is about DEPTH — a WIDE document (millions of sibling elements, never nesting past a handful of levels) sails straight through it. `convertChildren` had no count of its own at all. Confirmed live, two ways: `root.innerHTML = "
x
".repeat(3000000)` — one statement, no loop, the repeated fragment a mere 13 bytes — allocated **~11.5GB of real memory in 15 seconds**; a 25-iteration `root.innerHTML = root.outerHTML + root.outerHTML` loop (the same doubling shape as round 172's `cloneNode` case, but through serialize-then-reparse instead of direct cloning) reached ~6GB. +- **Fixed**: `convertChildren` now carries a shared node-count `budget` (`maxParsedNodes = 200,000`, matching `js.maxCloneNodes`'s own value and reasoning — the same bug, one round earlier, for `cloneNode`'s bushy-growth case) threaded through both its own recursion and the `