Found while fixing #342 (#481), which does not change it.
TagBalancingHtmlStreamEventReceiver writes a start tag when the stack has fewer than nestingLimit entries, so the element at index nestingLimit - 1 is written. But the close paths gate on openElements.size() < nestingLimit before the pop, in prepareForContent, and on last + 1 < nestingLimit in closeTag, which is one stricter, so that element's end tag is never written when it is closed by an end tag or pushed out by content it cannot hold. The element after it is then written inside it, and the output is unbalanced:
balancer.setNestingLimit(3);
// <div><div><span>x</span><p>y</p></div></div>
// now: <div><div><span>x<p>y</div></div>
closeDocument uses Math.min(nestingLimit, size) and so gets it right for whatever is still open at the end. The policy and renderer downstream see the same unbalanced events, and a browser closes the elements itself, so the sanitizer's own model stays consistent with the browser; but the balancer's stack no longer matches the output from that point, and elements the author closed stay open in the output. The default limit is 256, so this needs input nested that deep. The fix is to gate every close on the index the element was written at, index < nestingLimit, as #481 does for the tags it adds. Reproduced on main at fad01c1.
Found while fixing #342 (#481), which does not change it.
TagBalancingHtmlStreamEventReceiverwrites a start tag when the stack has fewer thannestingLimitentries, so the element at indexnestingLimit - 1is written. But the close paths gate onopenElements.size() < nestingLimitbefore the pop, inprepareForContent, and onlast + 1 < nestingLimitincloseTag, which is one stricter, so that element's end tag is never written when it is closed by an end tag or pushed out by content it cannot hold. The element after it is then written inside it, and the output is unbalanced:closeDocumentusesMath.min(nestingLimit, size)and so gets it right for whatever is still open at the end. The policy and renderer downstream see the same unbalanced events, and a browser closes the elements itself, so the sanitizer's own model stays consistent with the browser; but the balancer's stack no longer matches the output from that point, and elements the author closed stay open in the output. The default limit is 256, so this needs input nested that deep. The fix is to gate every close on the index the element was written at,index < nestingLimit, as #481 does for the tags it adds. Reproduced onmainat fad01c1.